Initial commit

This commit is contained in:
jiapengyu
2026-07-27 13:37:48 +08:00
commit ecba71e2a5
39123 changed files with 5989154 additions and 0 deletions
+396
View File
@@ -0,0 +1,396 @@
IF(DEBUG_POOL)
SET(GIT_DEBUG_POOL 1)
ENDIF()
ADD_FEATURE_INFO(debugpool GIT_DEBUG_POOL "debug pool allocator")
INCLUDE(PkgBuildConfig)
# This variable will contain the libraries we need to put into
# libgit2.pc's Requires.private. That is, what we're linking to or
# what someone who's statically linking us needs to link to.
SET(LIBGIT2_PC_REQUIRES "")
# This will be set later if we use the system's http-parser library or
# use iconv (OSX) and will be written to the Libs.private field in the
# pc file.
SET(LIBGIT2_PC_LIBS "")
SET(LIBGIT2_INCLUDES
"${CMAKE_CURRENT_BINARY_DIR}"
"${libgit2_SOURCE_DIR}/src"
"${libgit2_SOURCE_DIR}/include")
SET(LIBGIT2_SYSTEM_INCLUDES "")
SET(LIBGIT2_LIBS "")
# Installation paths
#
SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.")
SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.")
SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.")
# Enable tracing
IF (ENABLE_TRACE STREQUAL "ON")
SET(GIT_TRACE 1)
ENDIF()
ADD_FEATURE_INFO(tracing GIT_TRACE "tracing support")
CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS)
IF (HAVE_FUTIMENS)
SET(GIT_USE_FUTIMENS 1)
ENDIF ()
CHECK_PROTOTYPE_DEFINITION(qsort_r
"void qsort_r(void *base, size_t nmemb, size_t size, void *thunk, int (*compar)(void *, const void *, const void *))"
"" "stdlib.h" HAVE_QSORT_R_BSD)
IF (HAVE_QSORT_R_BSD)
ADD_DEFINITIONS(-DHAVE_QSORT_R_BSD)
ENDIF()
CHECK_PROTOTYPE_DEFINITION(qsort_r
"void qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *, void *), void *arg)"
"" "stdlib.h" HAVE_QSORT_R_GNU)
IF (HAVE_QSORT_R_GNU)
ADD_DEFINITIONS(-DHAVE_QSORT_R_GNU)
ENDIF()
CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S)
IF (HAVE_QSORT_S)
ADD_DEFINITIONS(-DHAVE_QSORT_S)
ENDIF ()
# Find required dependencies
IF(WIN32)
LIST(APPEND LIBGIT2_LIBS ws2_32)
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)")
LIST(APPEND LIBGIT2_LIBS socket nsl)
LIST(APPEND LIBGIT2_PC_LIBS "-lsocket" "-lnsl")
ELSEIF(CMAKE_SYSTEM_NAME MATCHES "Haiku")
LIST(APPEND LIBGIT2_LIBS network)
LIST(APPEND LIBGIT2_PC_LIBS "-lnetwork")
ENDIF()
CHECK_LIBRARY_EXISTS(rt clock_gettime "time.h" NEED_LIBRT)
IF(NEED_LIBRT)
LIST(APPEND LIBGIT2_LIBS rt)
LIST(APPEND LIBGIT2_PC_LIBS "-lrt")
ENDIF()
IF(THREADSAFE)
LIST(APPEND LIBGIT2_LIBS ${CMAKE_THREAD_LIBS_INIT})
LIST(APPEND LIBGIT2_PC_LIBS ${CMAKE_THREAD_LIBS_INIT})
ENDIF()
ADD_FEATURE_INFO(threadsafe THREADSAFE "threadsafe support")
IF (WIN32 AND EMBED_SSH_PATH)
FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c")
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES "${EMBED_SSH_PATH}/include")
FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"")
SET(GIT_SSH 1)
ENDIF()
IF (WIN32 AND WINHTTP)
SET(GIT_WINHTTP 1)
# Since MinGW does not come with headers or an import library for winhttp,
# we have to include a private header and generate our own import library
IF (MINGW)
ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/winhttp" "${libgit2_BINARY_DIR}/deps/winhttp")
LIST(APPEND LIBGIT2_LIBS winhttp)
LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/winhttp")
ELSE()
LIST(APPEND LIBGIT2_LIBS "winhttp")
LIST(APPEND LIBGIT2_PC_LIBS "-lwinhttp")
ENDIF ()
LIST(APPEND LIBGIT2_LIBS "rpcrt4" "crypt32" "ole32")
LIST(APPEND LIBGIT2_PC_LIBS "-lrpcrt4" "-lcrypt32" "-lole32")
ENDIF()
Include(SelectHTTPSBackend)
Include(SelectHashes)
# Specify regular expression implementation
FIND_PACKAGE(PCRE)
IF(REGEX_BACKEND STREQUAL "")
CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L)
IF(HAVE_REGCOMP_L)
SET(REGEX_BACKEND "regcomp_l")
ELSEIF(PCRE_FOUND)
SET(REGEX_BACKEND "pcre")
ELSE()
SET(REGEX_BACKEND "builtin")
ENDIF()
ENDIF()
IF(REGEX_BACKEND STREQUAL "regcomp_l")
ADD_FEATURE_INFO(regex ON "using system regcomp_l")
SET(GIT_REGEX_REGCOMP_L 1)
ELSEIF(REGEX_BACKEND STREQUAL "pcre2")
FIND_PACKAGE(PCRE2)
IF(NOT PCRE2_FOUND)
MESSAGE(FATAL_ERROR "PCRE2 support was requested but not found")
ENDIF()
ADD_FEATURE_INFO(regex ON "using system PCRE2")
SET(GIT_REGEX_PCRE2 1)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${PCRE2_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${PCRE2_LIBRARIES})
LIST(APPEND LIBGIT2_PC_REQUIRES "libpcre2")
ELSEIF(REGEX_BACKEND STREQUAL "pcre")
ADD_FEATURE_INFO(regex ON "using system PCRE")
SET(GIT_REGEX_PCRE 1)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${PCRE_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${PCRE_LIBRARIES})
LIST(APPEND LIBGIT2_PC_REQUIRES "libpcre")
ELSEIF(REGEX_BACKEND STREQUAL "regcomp")
ADD_FEATURE_INFO(regex ON "using system regcomp")
SET(GIT_REGEX_REGCOMP 1)
ELSEIF(REGEX_BACKEND STREQUAL "builtin")
ADD_FEATURE_INFO(regex ON "using bundled PCRE")
SET(GIT_REGEX_BUILTIN 1)
ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/pcre" "${libgit2_BINARY_DIR}/deps/pcre")
LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/pcre")
LIST(APPEND LIBGIT2_OBJECTS $<TARGET_OBJECTS:pcre>)
ELSE()
MESSAGE(FATAL_ERROR "The REGEX_BACKEND option provided is not supported")
ENDIF()
# Optional external dependency: http-parser
IF(USE_HTTP_PARSER STREQUAL "system")
FIND_PACKAGE(HTTP_Parser)
IF (HTTP_PARSER_FOUND AND HTTP_PARSER_VERSION_MAJOR EQUAL 2)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${HTTP_PARSER_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${HTTP_PARSER_LIBRARIES})
LIST(APPEND LIBGIT2_PC_LIBS "-lhttp_parser")
ADD_FEATURE_INFO(http-parser ON "http-parser support (system)")
ELSE()
MESSAGE(FATAL_ERROR "http-parser support was requested but not found")
ENDIF()
ELSE()
MESSAGE(STATUS "http-parser version 2 was not found or disabled; using bundled 3rd-party sources.")
ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/http-parser" "${libgit2_BINARY_DIR}/deps/http-parser")
LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/http-parser")
LIST(APPEND LIBGIT2_OBJECTS "$<TARGET_OBJECTS:http-parser>")
ADD_FEATURE_INFO(http-parser ON "http-parser support (bundled)")
ENDIF()
# Optional external dependency: zlib
IF(NOT USE_BUNDLED_ZLIB)
FIND_PACKAGE(ZLIB)
IF(ZLIB_FOUND)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${ZLIB_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${ZLIB_LIBRARIES})
IF(APPLE OR CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
LIST(APPEND LIBGIT2_PC_LIBS "-lz")
ELSE()
LIST(APPEND LIBGIT2_PC_REQUIRES "zlib")
ENDIF()
ADD_FEATURE_INFO(zlib ON "using system zlib")
ELSE()
MESSAGE(STATUS "zlib was not found; using bundled 3rd-party sources." )
ENDIF()
ENDIF()
IF(USE_BUNDLED_ZLIB OR NOT ZLIB_FOUND)
ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/zlib" "${libgit2_BINARY_DIR}/deps/zlib")
LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/zlib")
LIST(APPEND LIBGIT2_OBJECTS $<TARGET_OBJECTS:zlib>)
ADD_FEATURE_INFO(zlib ON "using bundled zlib")
ENDIF()
# Optional external dependency: libssh2
IF (USE_SSH)
FIND_PKGLIBRARIES(LIBSSH2 libssh2)
ENDIF()
IF (LIBSSH2_FOUND)
SET(GIT_SSH 1)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${LIBSSH2_INCLUDE_DIRS})
LIST(APPEND LIBGIT2_LIBS ${LIBSSH2_LIBRARIES})
LIST(APPEND LIBGIT2_PC_LIBS ${LIBSSH2_LDFLAGS})
CHECK_LIBRARY_EXISTS("${LIBSSH2_LIBRARIES}" libssh2_userauth_publickey_frommemory "${LIBSSH2_LIBRARY_DIRS}" HAVE_LIBSSH2_MEMORY_CREDENTIALS)
IF (HAVE_LIBSSH2_MEMORY_CREDENTIALS)
SET(GIT_SSH_MEMORY_CREDENTIALS 1)
ENDIF()
ELSE()
MESSAGE(STATUS "LIBSSH2 not found. Set CMAKE_PREFIX_PATH if it is installed outside of the default search path.")
ENDIF()
ADD_FEATURE_INFO(SSH GIT_SSH "SSH transport support")
# Optional external dependency: ntlmclient
IF (USE_NTLMCLIENT)
SET(GIT_NTLM 1)
ADD_SUBDIRECTORY("${libgit2_SOURCE_DIR}/deps/ntlmclient" "${libgit2_BINARY_DIR}/deps/ntlmclient")
LIST(APPEND LIBGIT2_INCLUDES "${libgit2_SOURCE_DIR}/deps/ntlmclient")
LIST(APPEND LIBGIT2_OBJECTS "$<TARGET_OBJECTS:ntlmclient>")
ENDIF()
ADD_FEATURE_INFO(ntlmclient GIT_NTLM "NTLM authentication support for Unix")
# Optional external dependency: GSSAPI
INCLUDE(SelectGSSAPI)
# Optional external dependency: iconv
IF (USE_ICONV)
FIND_PACKAGE(Iconv)
ENDIF()
IF (ICONV_FOUND)
SET(GIT_USE_ICONV 1)
LIST(APPEND LIBGIT2_SYSTEM_INCLUDES ${ICONV_INCLUDE_DIR})
LIST(APPEND LIBGIT2_LIBS ${ICONV_LIBRARIES})
LIST(APPEND LIBGIT2_PC_LIBS ${ICONV_LIBRARIES})
ENDIF()
ADD_FEATURE_INFO(iconv GIT_USE_ICONV "iconv encoding conversion support")
IF (THREADSAFE)
IF (NOT WIN32)
FIND_PACKAGE(Threads REQUIRED)
ENDIF()
SET(GIT_THREADS 1)
ENDIF()
IF (USE_NSEC)
SET(GIT_USE_NSEC 1)
ENDIF()
IF (HAVE_STRUCT_STAT_ST_MTIM)
SET(GIT_USE_STAT_MTIM 1)
ELSEIF (HAVE_STRUCT_STAT_ST_MTIMESPEC)
SET(GIT_USE_STAT_MTIMESPEC 1)
ELSEIF (HAVE_STRUCT_STAT_ST_MTIME_NSEC)
SET(GIT_USE_STAT_MTIME_NSEC 1)
ENDIF()
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64)
# Collect sourcefiles
FILE(GLOB SRC_H
"${libgit2_SOURCE_DIR}/include/git2.h"
"${libgit2_SOURCE_DIR}/include/git2/*.h"
"${libgit2_SOURCE_DIR}/include/git2/sys/*.h")
# On Windows use specific platform sources
IF (WIN32 AND NOT CYGWIN)
IF(MSVC)
SET(WIN_RC "win32/git2.rc")
ENDIF()
FILE(GLOB SRC_OS win32/*.c win32/*.h)
ELSEIF (AMIGA)
ADD_DEFINITIONS(-DNO_ADDRINFO -DNO_READDIR_R -DNO_MMAP)
ELSE()
ADD_FEATURE_INFO(valgrind VALGRIND "valgrind hints")
IF (VALGRIND)
ADD_DEFINITIONS(-DVALGRIND)
ENDIF()
FILE(GLOB SRC_OS unix/*.c unix/*.h)
ENDIF()
FILE(GLOB SRC_GIT2 *.c *.h
allocators/*.c allocators/*.h
streams/*.c streams/*.h
transports/*.c transports/*.h
xdiff/*.c xdiff/*.h)
# the xdiff dependency is not (yet) warning-free, disable warnings as
# errors for the xdiff sources until we've sorted them out
IF(MSVC)
SET_SOURCE_FILES_PROPERTIES(xdiff/xdiffi.c PROPERTIES COMPILE_FLAGS -WX-)
SET_SOURCE_FILES_PROPERTIES(xdiff/xutils.c PROPERTIES COMPILE_FLAGS -WX-)
ENDIF()
# Determine architecture of the machine
IF (CMAKE_SIZEOF_VOID_P EQUAL 8)
SET(GIT_ARCH_64 1)
ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4)
SET(GIT_ARCH_32 1)
ELSEIF (CMAKE_SIZEOF_VOID_P)
MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)")
ELSE()
MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)")
ENDIF()
CONFIGURE_FILE(features.h.in git2/sys/features.h)
SET(LIBGIT2_SOURCES ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_SSH} ${SRC_SHA1})
ADD_LIBRARY(git2internal OBJECT ${LIBGIT2_SOURCES})
SET_TARGET_PROPERTIES(git2internal PROPERTIES C_STANDARD 90)
IDE_SPLIT_SOURCES(git2internal)
LIST(APPEND LIBGIT2_OBJECTS $<TARGET_OBJECTS:git2internal>)
TARGET_INCLUDE_DIRECTORIES(git2internal PRIVATE ${LIBGIT2_INCLUDES} PUBLIC ${libgit2_SOURCE_DIR}/include)
TARGET_INCLUDE_DIRECTORIES(git2internal SYSTEM PRIVATE ${LIBGIT2_SYSTEM_INCLUDES})
SET(LIBGIT2_OBJECTS ${LIBGIT2_OBJECTS} PARENT_SCOPE)
SET(LIBGIT2_INCLUDES ${LIBGIT2_INCLUDES} PARENT_SCOPE)
SET(LIBGIT2_SYSTEM_INCLUDES ${LIBGIT2_SYSTEM_INCLUDES} PARENT_SCOPE)
SET(LIBGIT2_LIBS ${LIBGIT2_LIBS} PARENT_SCOPE)
IF(XCODE_VERSION)
# This is required for Xcode to actually link the libgit2 library
# when using only object libraries.
FILE(WRITE ${CMAKE_CURRENT_BINARY_DIR}/dummy.c "")
LIST(APPEND LIBGIT2_OBJECTS ${CMAKE_CURRENT_BINARY_DIR}/dummy.c)
ENDIF()
# Compile and link libgit2
ADD_LIBRARY(git2 ${WIN_RC} ${LIBGIT2_OBJECTS})
TARGET_LINK_LIBRARIES(git2 ${LIBGIT2_LIBS})
SET_TARGET_PROPERTIES(git2 PROPERTIES C_STANDARD 90)
SET_TARGET_PROPERTIES(git2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR})
SET_TARGET_PROPERTIES(git2 PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR})
SET_TARGET_PROPERTIES(git2 PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${libgit2_BINARY_DIR})
# Workaround for Cmake bug #0011240 (see http://public.kitware.com/Bug/view.php?id=11240)
# Win64+MSVC+static libs = linker error
IF(MSVC AND GIT_ARCH_64 AND NOT BUILD_SHARED_LIBS)
SET_TARGET_PROPERTIES(git2 PROPERTIES STATIC_LIBRARY_FLAGS "/MACHINE:x64")
ENDIF()
IDE_SPLIT_SOURCES(git2)
IF (SONAME)
SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING})
SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_SOVERSION})
IF (LIBGIT2_FILENAME)
ADD_DEFINITIONS(-DLIBGIT2_FILENAME=\"${LIBGIT2_FILENAME}\")
SET_TARGET_PROPERTIES(git2 PROPERTIES OUTPUT_NAME ${LIBGIT2_FILENAME})
ELSEIF (DEFINED LIBGIT2_PREFIX)
SET_TARGET_PROPERTIES(git2 PROPERTIES PREFIX "${LIBGIT2_PREFIX}")
ENDIF()
ENDIF()
PKG_BUILD_CONFIG(NAME libgit2
VERSION ${LIBGIT2_VERSION_STRING}
DESCRIPTION "The git library, take 2"
LIBS_SELF git2
PRIVATE_LIBS ${LIBGIT2_PC_LIBS}
REQUIRES ${LIBGIT2_PC_REQUIRES}
)
IF (MSVC_IDE)
# Precompiled headers
SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h")
SET_SOURCE_FILES_PROPERTIES(win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h")
ENDIF ()
# Install
INSTALL(TARGETS git2
RUNTIME DESTINATION ${BIN_INSTALL_DIR}
LIBRARY DESTINATION ${LIB_INSTALL_DIR}
ARCHIVE DESTINATION ${LIB_INSTALL_DIR}
)
INSTALL(DIRECTORY ${libgit2_SOURCE_DIR}/include/git2 DESTINATION ${INCLUDE_INSTALL_DIR} )
INSTALL(FILES ${libgit2_SOURCE_DIR}/include/git2.h DESTINATION ${INCLUDE_INSTALL_DIR} )
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "alloc.h"
#include "allocators/stdalloc.h"
#include "allocators/win32_crtdbg.h"
git_allocator git__allocator;
static int setup_default_allocator(void)
{
#if defined(GIT_MSVC_CRTDBG)
return git_win32_crtdbg_init_allocator(&git__allocator);
#else
return git_stdalloc_init_allocator(&git__allocator);
#endif
}
int git_allocator_global_init(void)
{
/*
* We don't want to overwrite any allocator which has been set before
* the init function is called.
*/
if (git__allocator.gmalloc != NULL)
return 0;
return setup_default_allocator();
}
int git_allocator_setup(git_allocator *allocator)
{
if (!allocator)
return setup_default_allocator();
memcpy(&git__allocator, allocator, sizeof(*allocator));
return 0;
}
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_alloc_h__
#define INCLUDE_alloc_h__
#include "git2/sys/alloc.h"
extern git_allocator git__allocator;
#define git__malloc(len) git__allocator.gmalloc(len, __FILE__, __LINE__)
#define git__calloc(nelem, elsize) git__allocator.gcalloc(nelem, elsize, __FILE__, __LINE__)
#define git__strdup(str) git__allocator.gstrdup(str, __FILE__, __LINE__)
#define git__strndup(str, n) git__allocator.gstrndup(str, n, __FILE__, __LINE__)
#define git__substrdup(str, n) git__allocator.gsubstrdup(str, n, __FILE__, __LINE__)
#define git__realloc(ptr, size) git__allocator.grealloc(ptr, size, __FILE__, __LINE__)
#define git__reallocarray(ptr, nelem, elsize) git__allocator.greallocarray(ptr, nelem, elsize, __FILE__, __LINE__)
#define git__mallocarray(nelem, elsize) git__allocator.gmallocarray(nelem, elsize, __FILE__, __LINE__)
#define git__free git__allocator.gfree
/**
* This function is being called by our global setup routines to
* initialize the standard allocator.
*/
int git_allocator_global_init(void);
/**
* Switch out libgit2's global memory allocator
*
* @param allocator The new allocator that should be used. All function pointers
* of it need to be set correctly.
* @return An error code or 0.
*/
int git_allocator_setup(git_allocator *allocator);
#endif
+119
View File
@@ -0,0 +1,119 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "stdalloc.h"
static void *stdalloc__malloc(size_t len, const char *file, int line)
{
void *ptr = malloc(len);
GIT_UNUSED(file);
GIT_UNUSED(line);
if (!ptr) git_error_set_oom();
return ptr;
}
static void *stdalloc__calloc(size_t nelem, size_t elsize, const char *file, int line)
{
void *ptr = calloc(nelem, elsize);
GIT_UNUSED(file);
GIT_UNUSED(line);
if (!ptr) git_error_set_oom();
return ptr;
}
static char *stdalloc__strdup(const char *str, const char *file, int line)
{
char *ptr = strdup(str);
GIT_UNUSED(file);
GIT_UNUSED(line);
if (!ptr) git_error_set_oom();
return ptr;
}
static char *stdalloc__strndup(const char *str, size_t n, const char *file, int line)
{
size_t length = 0, alloclength;
char *ptr;
length = p_strnlen(str, n);
if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) ||
!(ptr = stdalloc__malloc(alloclength, file, line)))
return NULL;
if (length)
memcpy(ptr, str, length);
ptr[length] = '\0';
return ptr;
}
static char *stdalloc__substrdup(const char *start, size_t n, const char *file, int line)
{
char *ptr;
size_t alloclen;
if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) ||
!(ptr = stdalloc__malloc(alloclen, file, line)))
return NULL;
memcpy(ptr, start, n);
ptr[n] = '\0';
return ptr;
}
static void *stdalloc__realloc(void *ptr, size_t size, const char *file, int line)
{
void *new_ptr = realloc(ptr, size);
GIT_UNUSED(file);
GIT_UNUSED(line);
if (!new_ptr) git_error_set_oom();
return new_ptr;
}
static void *stdalloc__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line)
{
size_t newsize;
if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize))
return NULL;
return stdalloc__realloc(ptr, newsize, file, line);
}
static void *stdalloc__mallocarray(size_t nelem, size_t elsize, const char *file, int line)
{
return stdalloc__reallocarray(NULL, nelem, elsize, file, line);
}
static void stdalloc__free(void *ptr)
{
free(ptr);
}
int git_stdalloc_init_allocator(git_allocator *allocator)
{
allocator->gmalloc = stdalloc__malloc;
allocator->gcalloc = stdalloc__calloc;
allocator->gstrdup = stdalloc__strdup;
allocator->gstrndup = stdalloc__strndup;
allocator->gsubstrdup = stdalloc__substrdup;
allocator->grealloc = stdalloc__realloc;
allocator->greallocarray = stdalloc__reallocarray;
allocator->gmallocarray = stdalloc__mallocarray;
allocator->gfree = stdalloc__free;
return 0;
}
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_allocators_stdalloc_h__
#define INCLUDE_allocators_stdalloc_h__
#include "common.h"
#include "alloc.h"
int git_stdalloc_init_allocator(git_allocator *allocator);
#endif
+118
View File
@@ -0,0 +1,118 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "win32_crtdbg.h"
#if defined(GIT_MSVC_CRTDBG)
#include "win32/w32_crtdbg_stacktrace.h"
static void *crtdbg__malloc(size_t len, const char *file, int line)
{
void *ptr = _malloc_dbg(len, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
static void *crtdbg__calloc(size_t nelem, size_t elsize, const char *file, int line)
{
void *ptr = _calloc_dbg(nelem, elsize, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
static char *crtdbg__strdup(const char *str, const char *file, int line)
{
char *ptr = _strdup_dbg(str, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
if (!ptr) git_error_set_oom();
return ptr;
}
static char *crtdbg__strndup(const char *str, size_t n, const char *file, int line)
{
size_t length = 0, alloclength;
char *ptr;
length = p_strnlen(str, n);
if (GIT_ADD_SIZET_OVERFLOW(&alloclength, length, 1) ||
!(ptr = crtdbg__malloc(alloclength, file, line)))
return NULL;
if (length)
memcpy(ptr, str, length);
ptr[length] = '\0';
return ptr;
}
static char *crtdbg__substrdup(const char *start, size_t n, const char *file, int line)
{
char *ptr;
size_t alloclen;
if (GIT_ADD_SIZET_OVERFLOW(&alloclen, n, 1) ||
!(ptr = crtdbg__malloc(alloclen, file, line)))
return NULL;
memcpy(ptr, start, n);
ptr[n] = '\0';
return ptr;
}
static void *crtdbg__realloc(void *ptr, size_t size, const char *file, int line)
{
void *new_ptr = _realloc_dbg(ptr, size, _NORMAL_BLOCK, git_win32__crtdbg_stacktrace(1,file), line);
if (!new_ptr) git_error_set_oom();
return new_ptr;
}
static void *crtdbg__reallocarray(void *ptr, size_t nelem, size_t elsize, const char *file, int line)
{
size_t newsize;
if (GIT_MULTIPLY_SIZET_OVERFLOW(&newsize, nelem, elsize))
return NULL;
return crtdbg__realloc(ptr, newsize, file, line);
}
static void *crtdbg__mallocarray(size_t nelem, size_t elsize, const char *file, int line)
{
return crtdbg__reallocarray(NULL, nelem, elsize, file, line);
}
static void crtdbg__free(void *ptr)
{
free(ptr);
}
int git_win32_crtdbg_init_allocator(git_allocator *allocator)
{
allocator->gmalloc = crtdbg__malloc;
allocator->gcalloc = crtdbg__calloc;
allocator->gstrdup = crtdbg__strdup;
allocator->gstrndup = crtdbg__strndup;
allocator->gsubstrdup = crtdbg__substrdup;
allocator->grealloc = crtdbg__realloc;
allocator->greallocarray = crtdbg__reallocarray;
allocator->gmallocarray = crtdbg__mallocarray;
allocator->gfree = crtdbg__free;
return 0;
}
#else
int git_win32_crtdbg_init_allocator(git_allocator *allocator)
{
GIT_UNUSED(allocator);
git_error_set(GIT_EINVALID, "crtdbg memory allocator not available");
return -1;
}
#endif
+17
View File
@@ -0,0 +1,17 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_allocators_crtdbg_h
#define INCLUDE_allocators_crtdbg_h
#include "common.h"
#include "alloc.h"
int git_win32_crtdbg_init_allocator(git_allocator *allocator);
#endif
+228
View File
@@ -0,0 +1,228 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "annotated_commit.h"
#include "refs.h"
#include "cache.h"
#include "git2/commit.h"
#include "git2/refs.h"
#include "git2/repository.h"
#include "git2/annotated_commit.h"
#include "git2/revparse.h"
#include "git2/tree.h"
#include "git2/index.h"
static int annotated_commit_init(
git_annotated_commit **out,
git_commit *commit,
const char *description)
{
git_annotated_commit *annotated_commit;
int error = 0;
assert(out && commit);
*out = NULL;
annotated_commit = git__calloc(1, sizeof(git_annotated_commit));
GIT_ERROR_CHECK_ALLOC(annotated_commit);
annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL;
if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0)
goto done;
git_oid_fmt(annotated_commit->id_str, git_commit_id(commit));
annotated_commit->id_str[GIT_OID_HEXSZ] = '\0';
if (!description)
description = annotated_commit->id_str;
annotated_commit->description = git__strdup(description);
GIT_ERROR_CHECK_ALLOC(annotated_commit->description);
done:
if (!error)
*out = annotated_commit;
return error;
}
static int annotated_commit_init_from_id(
git_annotated_commit **out,
git_repository *repo,
const git_oid *id,
const char *description)
{
git_commit *commit = NULL;
int error = 0;
assert(out && repo && id);
*out = NULL;
if ((error = git_commit_lookup(&commit, repo, id)) < 0)
goto done;
error = annotated_commit_init(out, commit, description);
done:
git_commit_free(commit);
return error;
}
int git_annotated_commit_lookup(
git_annotated_commit **out,
git_repository *repo,
const git_oid *id)
{
return annotated_commit_init_from_id(out, repo, id, NULL);
}
int git_annotated_commit_from_commit(
git_annotated_commit **out,
git_commit *commit)
{
return annotated_commit_init(out, commit, NULL);
}
int git_annotated_commit_from_revspec(
git_annotated_commit **out,
git_repository *repo,
const char *revspec)
{
git_object *obj, *commit;
int error;
assert(out && repo && revspec);
if ((error = git_revparse_single(&obj, repo, revspec)) < 0)
return error;
if ((error = git_object_peel(&commit, obj, GIT_OBJECT_COMMIT))) {
git_object_free(obj);
return error;
}
error = annotated_commit_init(out, (git_commit *)commit, revspec);
git_object_free(obj);
git_object_free(commit);
return error;
}
int git_annotated_commit_from_ref(
git_annotated_commit **out,
git_repository *repo,
const git_reference *ref)
{
git_object *peeled;
int error = 0;
assert(out && repo && ref);
*out = NULL;
if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_COMMIT)) < 0)
return error;
error = annotated_commit_init_from_id(out,
repo,
git_object_id(peeled),
git_reference_name(ref));
if (!error) {
(*out)->ref_name = git__strdup(git_reference_name(ref));
GIT_ERROR_CHECK_ALLOC((*out)->ref_name);
}
git_object_free(peeled);
return error;
}
int git_annotated_commit_from_head(
git_annotated_commit **out,
git_repository *repo)
{
git_reference *head;
int error;
assert(out && repo);
*out = NULL;
if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0)
return -1;
error = git_annotated_commit_from_ref(out, repo, head);
git_reference_free(head);
return error;
}
int git_annotated_commit_from_fetchhead(
git_annotated_commit **out,
git_repository *repo,
const char *branch_name,
const char *remote_url,
const git_oid *id)
{
assert(repo && id && branch_name && remote_url);
if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0)
return -1;
(*out)->ref_name = git__strdup(branch_name);
GIT_ERROR_CHECK_ALLOC((*out)->ref_name);
(*out)->remote_url = git__strdup(remote_url);
GIT_ERROR_CHECK_ALLOC((*out)->remote_url);
return 0;
}
const git_oid *git_annotated_commit_id(
const git_annotated_commit *annotated_commit)
{
assert(annotated_commit);
return git_commit_id(annotated_commit->commit);
}
const char *git_annotated_commit_ref(
const git_annotated_commit *annotated_commit)
{
assert(annotated_commit);
return annotated_commit->ref_name;
}
void git_annotated_commit_free(git_annotated_commit *annotated_commit)
{
if (annotated_commit == NULL)
return;
switch (annotated_commit->type) {
case GIT_ANNOTATED_COMMIT_REAL:
git_commit_free(annotated_commit->commit);
git_tree_free(annotated_commit->tree);
git__free((char *)annotated_commit->description);
git__free((char *)annotated_commit->ref_name);
git__free((char *)annotated_commit->remote_url);
break;
case GIT_ANNOTATED_COMMIT_VIRTUAL:
git_index_free(annotated_commit->index);
git_array_clear(annotated_commit->parents);
break;
default:
abort();
}
git__free(annotated_commit);
}
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_annotated_commit_h__
#define INCLUDE_annotated_commit_h__
#include "common.h"
#include "oidarray.h"
#include "git2/oid.h"
typedef enum {
GIT_ANNOTATED_COMMIT_REAL = 1,
GIT_ANNOTATED_COMMIT_VIRTUAL = 2,
} git_annotated_commit_t;
/**
* Internal structure for merge inputs. An annotated commit is generally
* "real" and backed by an actual commit in the repository, but merge will
* internally create "virtual" commits that are in-memory intermediate
* commits backed by an index.
*/
struct git_annotated_commit {
git_annotated_commit_t type;
/* real commit */
git_commit *commit;
git_tree *tree;
/* virtual commit structure */
git_index *index;
git_array_oid_t parents;
/* how this commit was looked up */
const char *description;
const char *ref_name;
const char *remote_url;
char id_str[GIT_OID_HEXSZ+1];
};
extern int git_annotated_commit_from_head(git_annotated_commit **out,
git_repository *repo);
extern int git_annotated_commit_from_commit(git_annotated_commit **out,
git_commit *commit);
#endif
+885
View File
@@ -0,0 +1,885 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "apply.h"
#include "git2/apply.h"
#include "git2/patch.h"
#include "git2/filter.h"
#include "git2/blob.h"
#include "git2/index.h"
#include "git2/checkout.h"
#include "git2/repository.h"
#include "array.h"
#include "patch.h"
#include "futils.h"
#include "delta.h"
#include "zstream.h"
#include "reader.h"
#include "index.h"
typedef struct {
/* The lines that we allocate ourself are allocated out of the pool.
* (Lines may have been allocated out of the diff.)
*/
git_pool pool;
git_vector lines;
} patch_image;
static int apply_err(const char *fmt, ...) GIT_FORMAT_PRINTF(1, 2);
static int apply_err(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
git_error_vset(GIT_ERROR_PATCH, fmt, ap);
va_end(ap);
return GIT_EAPPLYFAIL;
}
static void patch_line_init(
git_diff_line *out,
const char *in,
size_t in_len,
size_t in_offset)
{
out->content = in;
out->content_len = in_len;
out->content_offset = in_offset;
}
#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT }
static int patch_image_init_fromstr(
patch_image *out, const char *in, size_t in_len)
{
git_diff_line *line;
const char *start, *end;
memset(out, 0x0, sizeof(patch_image));
git_pool_init(&out->pool, sizeof(git_diff_line));
for (start = in; start < in + in_len; start = end) {
end = memchr(start, '\n', in_len - (start - in));
if (end == NULL)
end = in + in_len;
else if (end < in + in_len)
end++;
line = git_pool_mallocz(&out->pool, 1);
GIT_ERROR_CHECK_ALLOC(line);
if (git_vector_insert(&out->lines, line) < 0)
return -1;
patch_line_init(line, start, (end - start), (start - in));
}
return 0;
}
static void patch_image_free(patch_image *image)
{
if (image == NULL)
return;
git_pool_clear(&image->pool);
git_vector_free(&image->lines);
}
static bool match_hunk(
patch_image *image,
patch_image *preimage,
size_t linenum)
{
bool match = 0;
size_t i;
/* Ensure this hunk is within the image boundaries. */
if (git_vector_length(&preimage->lines) + linenum >
git_vector_length(&image->lines))
return 0;
match = 1;
/* Check exact match. */
for (i = 0; i < git_vector_length(&preimage->lines); i++) {
git_diff_line *preimage_line = git_vector_get(&preimage->lines, i);
git_diff_line *image_line = git_vector_get(&image->lines, linenum + i);
if (preimage_line->content_len != image_line->content_len ||
memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) {
match = 0;
break;
}
}
return match;
}
static bool find_hunk_linenum(
size_t *out,
patch_image *image,
patch_image *preimage,
size_t linenum)
{
size_t max = git_vector_length(&image->lines);
bool match;
if (linenum > max)
linenum = max;
match = match_hunk(image, preimage, linenum);
*out = linenum;
return match;
}
static int update_hunk(
patch_image *image,
size_t linenum,
patch_image *preimage,
patch_image *postimage)
{
size_t postlen = git_vector_length(&postimage->lines);
size_t prelen = git_vector_length(&preimage->lines);
size_t i;
int error = 0;
if (postlen > prelen)
error = git_vector_insert_null(
&image->lines, linenum, (postlen - prelen));
else if (prelen > postlen)
error = git_vector_remove_range(
&image->lines, linenum, (prelen - postlen));
if (error) {
git_error_set_oom();
return -1;
}
for (i = 0; i < git_vector_length(&postimage->lines); i++) {
image->lines.contents[linenum + i] =
git_vector_get(&postimage->lines, i);
}
return 0;
}
typedef struct {
git_apply_options opts;
size_t skipped_new_lines;
size_t skipped_old_lines;
} apply_hunks_ctx;
static int apply_hunk(
patch_image *image,
git_patch *patch,
git_patch_hunk *hunk,
apply_hunks_ctx *ctx)
{
patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT;
size_t line_num, i;
int error = 0;
if (ctx->opts.hunk_cb) {
error = ctx->opts.hunk_cb(&hunk->hunk, ctx->opts.payload);
if (error) {
if (error > 0) {
ctx->skipped_new_lines += hunk->hunk.new_lines;
ctx->skipped_old_lines += hunk->hunk.old_lines;
error = 0;
}
goto done;
}
}
for (i = 0; i < hunk->line_count; i++) {
size_t linenum = hunk->line_start + i;
git_diff_line *line = git_array_get(patch->lines, linenum), *prev;
if (!line) {
error = apply_err("preimage does not contain line %"PRIuZ, linenum);
goto done;
}
switch (line->origin) {
case GIT_DIFF_LINE_CONTEXT_EOFNL:
case GIT_DIFF_LINE_DEL_EOFNL:
case GIT_DIFF_LINE_ADD_EOFNL:
prev = i ? git_array_get(patch->lines, linenum - 1) : NULL;
if (prev && prev->content[prev->content_len - 1] == '\n')
prev->content_len -= 1;
break;
case GIT_DIFF_LINE_CONTEXT:
if ((error = git_vector_insert(&preimage.lines, line)) < 0 ||
(error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
break;
case GIT_DIFF_LINE_DELETION:
if ((error = git_vector_insert(&preimage.lines, line)) < 0)
goto done;
break;
case GIT_DIFF_LINE_ADDITION:
if ((error = git_vector_insert(&postimage.lines, line)) < 0)
goto done;
break;
}
}
if (hunk->hunk.new_start) {
line_num = hunk->hunk.new_start -
ctx->skipped_new_lines +
ctx->skipped_old_lines -
1;
} else {
line_num = 0;
}
if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) {
error = apply_err("hunk at line %d did not apply",
hunk->hunk.new_start);
goto done;
}
error = update_hunk(image, line_num, &preimage, &postimage);
done:
patch_image_free(&preimage);
patch_image_free(&postimage);
return error;
}
static int apply_hunks(
git_buf *out,
const char *source,
size_t source_len,
git_patch *patch,
apply_hunks_ctx *ctx)
{
git_patch_hunk *hunk;
git_diff_line *line;
patch_image image;
size_t i;
int error = 0;
if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0)
goto done;
git_array_foreach(patch->hunks, i, hunk) {
if ((error = apply_hunk(&image, patch, hunk, ctx)) < 0)
goto done;
}
git_vector_foreach(&image.lines, i, line)
git_buf_put(out, line->content, line->content_len);
done:
patch_image_free(&image);
return error;
}
static int apply_binary_delta(
git_buf *out,
const char *source,
size_t source_len,
git_diff_binary_file *binary_file)
{
git_buf inflated = GIT_BUF_INIT;
int error = 0;
/* no diff means identical contents */
if (binary_file->datalen == 0)
return git_buf_put(out, source, source_len);
error = git_zstream_inflatebuf(&inflated,
binary_file->data, binary_file->datalen);
if (!error && inflated.size != binary_file->inflatedlen) {
error = apply_err("inflated delta does not match expected length");
git_buf_dispose(out);
}
if (error < 0)
goto done;
if (binary_file->type == GIT_DIFF_BINARY_DELTA) {
void *data;
size_t data_len;
error = git_delta_apply(&data, &data_len, (void *)source, source_len,
(void *)inflated.ptr, inflated.size);
out->ptr = data;
out->size = data_len;
out->asize = data_len;
}
else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) {
git_buf_swap(out, &inflated);
}
else {
error = apply_err("unknown binary delta type");
goto done;
}
done:
git_buf_dispose(&inflated);
return error;
}
static int apply_binary(
git_buf *out,
const char *source,
size_t source_len,
git_patch *patch)
{
git_buf reverse = GIT_BUF_INIT;
int error = 0;
if (!patch->binary.contains_data) {
error = apply_err("patch does not contain binary data");
goto done;
}
if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen)
goto done;
/* first, apply the new_file delta to the given source */
if ((error = apply_binary_delta(out, source, source_len,
&patch->binary.new_file)) < 0)
goto done;
/* second, apply the old_file delta to sanity check the result */
if ((error = apply_binary_delta(&reverse, out->ptr, out->size,
&patch->binary.old_file)) < 0)
goto done;
/* Verify that the resulting file with the reverse patch applied matches the source file */
if (source_len != reverse.size ||
(source_len && memcmp(source, reverse.ptr, source_len) != 0)) {
error = apply_err("binary patch did not apply cleanly");
goto done;
}
done:
if (error < 0)
git_buf_dispose(out);
git_buf_dispose(&reverse);
return error;
}
int git_apply__patch(
git_buf *contents_out,
char **filename_out,
unsigned int *mode_out,
const char *source,
size_t source_len,
git_patch *patch,
const git_apply_options *given_opts)
{
apply_hunks_ctx ctx = { GIT_APPLY_OPTIONS_INIT };
char *filename = NULL;
unsigned int mode = 0;
int error = 0;
assert(contents_out && filename_out && mode_out && (source || !source_len) && patch);
if (given_opts)
memcpy(&ctx.opts, given_opts, sizeof(git_apply_options));
*filename_out = NULL;
*mode_out = 0;
if (patch->delta->status != GIT_DELTA_DELETED) {
const git_diff_file *newfile = &patch->delta->new_file;
filename = git__strdup(newfile->path);
mode = newfile->mode ?
newfile->mode : GIT_FILEMODE_BLOB;
}
if (patch->delta->flags & GIT_DIFF_FLAG_BINARY)
error = apply_binary(contents_out, source, source_len, patch);
else if (patch->hunks.size)
error = apply_hunks(contents_out, source, source_len, patch, &ctx);
else
error = git_buf_put(contents_out, source, source_len);
if (error)
goto done;
if (patch->delta->status == GIT_DELTA_DELETED &&
git_buf_len(contents_out) > 0) {
error = apply_err("removal patch leaves file contents");
goto done;
}
*filename_out = filename;
*mode_out = mode;
done:
if (error < 0)
git__free(filename);
return error;
}
static int apply_one(
git_repository *repo,
git_reader *preimage_reader,
git_index *preimage,
git_reader *postimage_reader,
git_index *postimage,
git_diff *diff,
git_strmap *removed_paths,
size_t i,
const git_apply_options *opts)
{
git_patch *patch = NULL;
git_buf pre_contents = GIT_BUF_INIT, post_contents = GIT_BUF_INIT;
const git_diff_delta *delta;
char *filename = NULL;
unsigned int mode;
git_oid pre_id, post_id;
git_filemode_t pre_filemode;
git_index_entry pre_entry, post_entry;
bool skip_preimage = false;
int error;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
goto done;
delta = git_patch_get_delta(patch);
if (opts->delta_cb) {
error = opts->delta_cb(delta, opts->payload);
if (error) {
if (error > 0)
error = 0;
goto done;
}
}
/*
* Ensure that the file has not been deleted or renamed if we're
* applying a modification delta.
*/
if (delta->status != GIT_DELTA_RENAMED &&
delta->status != GIT_DELTA_ADDED) {
if (git_strmap_exists(removed_paths, delta->old_file.path)) {
error = apply_err("path '%s' has been renamed or deleted", delta->old_file.path);
goto done;
}
}
/*
* We may be applying a second delta to an already seen file. If so,
* use the already modified data in the postimage instead of the
* content from the index or working directory. (Don't do this in
* the case of a rename, which must be specified before additional
* deltas since we apply deltas to the target filename.)
*/
if (delta->status != GIT_DELTA_RENAMED) {
if ((error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
postimage_reader, delta->old_file.path)) == 0) {
skip_preimage = true;
} else if (error == GIT_ENOTFOUND) {
git_error_clear();
error = 0;
} else {
goto done;
}
}
if (!skip_preimage && delta->status != GIT_DELTA_ADDED) {
error = git_reader_read(&pre_contents, &pre_id, &pre_filemode,
preimage_reader, delta->old_file.path);
/* ENOTFOUND means the preimage was not found; apply failed. */
if (error == GIT_ENOTFOUND)
error = GIT_EAPPLYFAIL;
/* When applying to BOTH, the index did not match the workdir. */
if (error == GIT_READER_MISMATCH)
error = apply_err("%s: does not match index", delta->old_file.path);
if (error < 0)
goto done;
/*
* We need to populate the preimage data structure with the
* contents that we are using as the preimage for this file.
* This allows us to apply patches to files that have been
* modified in the working directory. During checkout,
* we will use this expected preimage as the baseline, and
* limit checkout to only the paths affected by patch
* application. (Without this, we would fail to write the
* postimage contents to any file that had been modified
* from HEAD on-disk, even if the patch application succeeded.)
* Use the contents from the delta where available - some
* fields may not be available, like the old file mode (eg in
* an exact rename situation) so trust the patch parsing to
* validate and use the preimage data in that case.
*/
if (preimage) {
memset(&pre_entry, 0, sizeof(git_index_entry));
pre_entry.path = delta->old_file.path;
pre_entry.mode = delta->old_file.mode ? delta->old_file.mode : pre_filemode;
git_oid_cpy(&pre_entry.id, &pre_id);
if ((error = git_index_add(preimage, &pre_entry)) < 0)
goto done;
}
}
if (delta->status != GIT_DELTA_DELETED) {
if ((error = git_apply__patch(&post_contents, &filename, &mode,
pre_contents.ptr, pre_contents.size, patch, opts)) < 0 ||
(error = git_blob_create_from_buffer(&post_id, repo,
post_contents.ptr, post_contents.size)) < 0)
goto done;
memset(&post_entry, 0, sizeof(git_index_entry));
post_entry.path = filename;
post_entry.mode = mode;
git_oid_cpy(&post_entry.id, &post_id);
if ((error = git_index_add(postimage, &post_entry)) < 0)
goto done;
}
if (delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_DELETED)
error = git_strmap_set(removed_paths, delta->old_file.path, (char *) delta->old_file.path);
if (delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_ADDED)
git_strmap_delete(removed_paths, delta->new_file.path);
done:
git_buf_dispose(&pre_contents);
git_buf_dispose(&post_contents);
git__free(filename);
git_patch_free(patch);
return error;
}
static int apply_deltas(
git_repository *repo,
git_reader *pre_reader,
git_index *preimage,
git_reader *post_reader,
git_index *postimage,
git_diff *diff,
const git_apply_options *opts)
{
git_strmap *removed_paths;
size_t i;
int error = 0;
if (git_strmap_new(&removed_paths) < 0)
return -1;
for (i = 0; i < git_diff_num_deltas(diff); i++) {
if ((error = apply_one(repo, pre_reader, preimage, post_reader, postimage, diff, removed_paths, i, opts)) < 0)
goto done;
}
done:
git_strmap_free(removed_paths);
return error;
}
int git_apply_to_tree(
git_index **out,
git_repository *repo,
git_tree *preimage,
git_diff *diff,
const git_apply_options *given_opts)
{
git_index *postimage = NULL;
git_reader *pre_reader = NULL, *post_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
const git_diff_delta *delta;
size_t i;
int error = 0;
assert(out && repo && preimage && diff);
*out = NULL;
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_apply_options));
if ((error = git_reader_for_tree(&pre_reader, preimage)) < 0)
goto done;
/*
* put the current tree into the postimage as-is - the diff will
* replace any entries contained therein
*/
if ((error = git_index_new(&postimage)) < 0 ||
(error = git_index_read_tree(postimage, preimage)) < 0 ||
(error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
goto done;
/*
* Remove the old paths from the index before applying diffs -
* we need to do a full pass to remove them before adding deltas,
* in order to handle rename situations.
*/
for (i = 0; i < git_diff_num_deltas(diff); i++) {
delta = git_diff_get_delta(diff, i);
if (delta->status == GIT_DELTA_DELETED ||
delta->status == GIT_DELTA_RENAMED) {
if ((error = git_index_remove(postimage,
delta->old_file.path, 0)) < 0)
goto done;
}
}
if ((error = apply_deltas(repo, pre_reader, NULL, post_reader, postimage, diff, &opts)) < 0)
goto done;
*out = postimage;
done:
if (error < 0)
git_index_free(postimage);
git_reader_free(pre_reader);
git_reader_free(post_reader);
return error;
}
static int git_apply__to_workdir(
git_repository *repo,
git_diff *diff,
git_index *preimage,
git_index *postimage,
git_apply_location_t location,
git_apply_options *opts)
{
git_vector paths = GIT_VECTOR_INIT;
git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT;
const git_diff_delta *delta;
size_t i;
int error;
GIT_UNUSED(opts);
/*
* Limit checkout to the paths affected by the diff; this ensures
* that other modifications in the working directory are unaffected.
*/
if ((error = git_vector_init(&paths, git_diff_num_deltas(diff), NULL)) < 0)
goto done;
for (i = 0; i < git_diff_num_deltas(diff); i++) {
delta = git_diff_get_delta(diff, i);
if ((error = git_vector_insert(&paths, (void *)delta->old_file.path)) < 0)
goto done;
if (strcmp(delta->old_file.path, delta->new_file.path) &&
(error = git_vector_insert(&paths, (void *)delta->new_file.path)) < 0)
goto done;
}
checkout_opts.checkout_strategy |= GIT_CHECKOUT_SAFE;
checkout_opts.checkout_strategy |= GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_WRITE_INDEX;
if (location == GIT_APPLY_LOCATION_WORKDIR)
checkout_opts.checkout_strategy |= GIT_CHECKOUT_DONT_UPDATE_INDEX;
checkout_opts.paths.strings = (char **)paths.contents;
checkout_opts.paths.count = paths.length;
checkout_opts.baseline_index = preimage;
error = git_checkout_index(repo, postimage, &checkout_opts);
done:
git_vector_free(&paths);
return error;
}
static int git_apply__to_index(
git_repository *repo,
git_diff *diff,
git_index *preimage,
git_index *postimage,
git_apply_options *opts)
{
git_index *index = NULL;
const git_diff_delta *delta;
const git_index_entry *entry;
size_t i;
int error;
GIT_UNUSED(preimage);
GIT_UNUSED(opts);
if ((error = git_repository_index(&index, repo)) < 0)
goto done;
/* Remove deleted (or renamed) paths from the index. */
for (i = 0; i < git_diff_num_deltas(diff); i++) {
delta = git_diff_get_delta(diff, i);
if (delta->status == GIT_DELTA_DELETED ||
delta->status == GIT_DELTA_RENAMED) {
if ((error = git_index_remove(index, delta->old_file.path, 0)) < 0)
goto done;
}
}
/* Then add the changes back to the index. */
for (i = 0; i < git_index_entrycount(postimage); i++) {
entry = git_index_get_byindex(postimage, i);
if ((error = git_index_add(index, entry)) < 0)
goto done;
}
done:
git_index_free(index);
return error;
}
int git_apply_options_init(git_apply_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_apply_options, GIT_APPLY_OPTIONS_INIT);
return 0;
}
/*
* Handle the three application options ("locations"):
*
* GIT_APPLY_LOCATION_WORKDIR: the default, emulates `git apply`.
* Applies the diff only to the workdir items and ignores the index
* entirely.
*
* GIT_APPLY_LOCATION_INDEX: emulates `git apply --cached`.
* Applies the diff only to the index items and ignores the workdir
* completely.
*
* GIT_APPLY_LOCATION_BOTH: emulates `git apply --index`.
* Applies the diff to both the index items and the working directory
* items.
*/
int git_apply(
git_repository *repo,
git_diff *diff,
git_apply_location_t location,
const git_apply_options *given_opts)
{
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
git_index *index = NULL, *preimage = NULL, *postimage = NULL;
git_reader *pre_reader = NULL, *post_reader = NULL;
git_apply_options opts = GIT_APPLY_OPTIONS_INIT;
int error = GIT_EINVALID;
assert(repo && diff);
GIT_ERROR_CHECK_VERSION(
given_opts, GIT_APPLY_OPTIONS_VERSION, "git_apply_options");
if (given_opts)
memcpy(&opts, given_opts, sizeof(git_apply_options));
/*
* by default, we apply a patch directly to the working directory;
* in `--cached` or `--index` mode, we apply to the contents already
* in the index.
*/
switch (location) {
case GIT_APPLY_LOCATION_BOTH:
error = git_reader_for_workdir(&pre_reader, repo, true);
break;
case GIT_APPLY_LOCATION_INDEX:
error = git_reader_for_index(&pre_reader, repo, NULL);
break;
case GIT_APPLY_LOCATION_WORKDIR:
error = git_reader_for_workdir(&pre_reader, repo, false);
break;
default:
assert(false);
}
if (error < 0)
goto done;
/*
* Build the preimage and postimage (differences). Note that
* this is not the complete preimage or postimage, it only
* contains the files affected by the patch. We want to avoid
* having the full repo index, so we will limit our checkout
* to only write these files that were affected by the diff.
*/
if ((error = git_index_new(&preimage)) < 0 ||
(error = git_index_new(&postimage)) < 0 ||
(error = git_reader_for_index(&post_reader, repo, postimage)) < 0)
goto done;
if (!(opts.flags & GIT_APPLY_CHECK))
if ((error = git_repository_index(&index, repo)) < 0 ||
(error = git_indexwriter_init(&indexwriter, index)) < 0)
goto done;
if ((error = apply_deltas(repo, pre_reader, preimage, post_reader, postimage, diff, &opts)) < 0)
goto done;
if ((opts.flags & GIT_APPLY_CHECK))
goto done;
switch (location) {
case GIT_APPLY_LOCATION_BOTH:
error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts);
break;
case GIT_APPLY_LOCATION_INDEX:
error = git_apply__to_index(repo, diff, preimage, postimage, &opts);
break;
case GIT_APPLY_LOCATION_WORKDIR:
error = git_apply__to_workdir(repo, diff, preimage, postimage, location, &opts);
break;
default:
assert(false);
}
if (error < 0)
goto done;
error = git_indexwriter_commit(&indexwriter);
done:
git_indexwriter_cleanup(&indexwriter);
git_index_free(postimage);
git_index_free(preimage);
git_index_free(index);
git_reader_free(pre_reader);
git_reader_free(post_reader);
return error;
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_apply_h__
#define INCLUDE_apply_h__
#include "common.h"
#include "git2/patch.h"
#include "git2/apply.h"
#include "buffer.h"
extern int git_apply__patch(
git_buf *out,
char **filename,
unsigned int *mode,
const char *source,
size_t source_len,
git_patch *patch,
const git_apply_options *opts);
#endif
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_array_h__
#define INCLUDE_array_h__
#include "common.h"
/*
* Use this to declare a typesafe resizable array of items, a la:
*
* git_array_t(int) my_ints = GIT_ARRAY_INIT;
* ...
* int *i = git_array_alloc(my_ints);
* GIT_ERROR_CHECK_ALLOC(i);
* ...
* git_array_clear(my_ints);
*
* You may also want to do things like:
*
* typedef git_array_t(my_struct) my_struct_array_t;
*/
#define git_array_t(type) struct { type *ptr; size_t size, asize; }
#define GIT_ARRAY_INIT { NULL, 0, 0 }
#define git_array_init(a) \
do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0)
#define git_array_init_to_size(a, desired) \
do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0)
#define git_array_clear(a) \
do { git__free((a).ptr); git_array_init(a); } while (0)
#define GIT_ERROR_CHECK_ARRAY(a) GIT_ERROR_CHECK_ALLOC((a).ptr)
typedef git_array_t(char) git_array_generic_t;
/* use a generic array for growth so this can return the new item */
GIT_INLINE(void *) git_array_grow(void *_a, size_t item_size)
{
volatile git_array_generic_t *a = _a;
size_t new_size;
char *new_array;
if (a->size < 8) {
new_size = 8;
} else {
if (GIT_MULTIPLY_SIZET_OVERFLOW(&new_size, a->size, 3))
goto on_oom;
new_size /= 2;
}
if ((new_array = git__reallocarray(a->ptr, new_size, item_size)) == NULL)
goto on_oom;
a->ptr = new_array; a->asize = new_size; a->size++;
return a->ptr + (a->size - 1) * item_size;
on_oom:
git_array_clear(*a);
return NULL;
}
#define git_array_alloc(a) \
(((a).size >= (a).asize) ? \
git_array_grow(&(a), sizeof(*(a).ptr)) : \
((a).ptr ? &(a).ptr[(a).size++] : NULL))
#define git_array_last(a) ((a).size ? &(a).ptr[(a).size - 1] : NULL)
#define git_array_pop(a) ((a).size ? &(a).ptr[--(a).size] : NULL)
#define git_array_get(a, i) (((i) < (a).size) ? &(a).ptr[(i)] : NULL)
#define git_array_size(a) (a).size
#define git_array_valid_index(a, i) ((i) < (a).size)
#define git_array_foreach(a, i, element) \
for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++)
GIT_INLINE(int) git_array__search(
size_t *out,
void *array_ptr,
size_t item_size,
size_t array_len,
int (*compare)(const void *, const void *),
const void *key)
{
size_t lim;
unsigned char *part, *array = array_ptr, *base = array_ptr;
int cmp = -1;
for (lim = array_len; lim != 0; lim >>= 1) {
part = base + (lim >> 1) * item_size;
cmp = (*compare)(key, part);
if (cmp == 0) {
base = part;
break;
}
if (cmp > 0) { /* key > p; take right partition */
base = part + 1 * item_size;
lim--;
} /* else take left partition */
}
if (out)
*out = (base - array) / item_size;
return (cmp == 0) ? 0 : GIT_ENOTFOUND;
}
#define git_array_search(out, a, cmp, key) \
git_array__search(out, (a).ptr, sizeof(*(a).ptr), (a).size, \
(cmp), (key))
#endif
+578
View File
@@ -0,0 +1,578 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "attr.h"
#include "repository.h"
#include "sysdir.h"
#include "config.h"
#include "attr_file.h"
#include "ignore.h"
#include "git2/oid.h"
#include <ctype.h>
const char *git_attr__true = "[internal]__TRUE__";
const char *git_attr__false = "[internal]__FALSE__";
const char *git_attr__unset = "[internal]__UNSET__";
git_attr_value_t git_attr_value(const char *attr)
{
if (attr == NULL || attr == git_attr__unset)
return GIT_ATTR_VALUE_UNSPECIFIED;
if (attr == git_attr__true)
return GIT_ATTR_VALUE_TRUE;
if (attr == git_attr__false)
return GIT_ATTR_VALUE_FALSE;
return GIT_ATTR_VALUE_STRING;
}
static int collect_attr_files(
git_repository *repo,
git_attr_session *attr_session,
uint32_t flags,
const char *path,
git_vector *files);
static void release_attr_files(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;
size_t i, j;
git_attr_file *file;
git_attr_name attr;
git_attr_rule *rule;
git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
assert(value && repo && name);
*value = NULL;
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0)
goto cleanup;
memset(&attr, 0, sizeof(attr));
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) {
size_t pos;
if (!git_vector_bsearch(&pos, &rule->assigns, &attr)) {
*value = ((git_attr_assignment *)git_vector_get(
&rule->assigns, pos))->value;
goto cleanup;
}
}
}
cleanup:
release_attr_files(&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_with_session(
const char **values,
git_repository *repo,
git_attr_session *attr_session,
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;
size_t i, j, k;
git_attr_file *file;
git_attr_rule *rule;
attr_get_many_info *info = NULL;
size_t num_found = 0;
git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
if (!num_attr)
return 0;
assert(values && repo && names);
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, attr_session, flags, pathname, &files)) < 0)
goto cleanup;
info = git__calloc(num_attr, sizeof(attr_get_many_info));
GIT_ERROR_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++) {
size_t 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]);
}
if (!git_vector_bsearch(&pos, &rule->assigns, &info[k].name)) {
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;
}
}
}
}
for (k = 0; k < num_attr; k++) {
if (!info[k].found)
values[k] = NULL;
}
cleanup:
release_attr_files(&files);
git_attr_path__free(&path);
git__free(info);
return error;
}
int git_attr_get_many(
const char **values,
git_repository *repo,
uint32_t flags,
const char *pathname,
size_t num_attr,
const char **names)
{
return git_attr_get_many_with_session(
values, repo, NULL, flags, pathname, num_attr, names);
}
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;
size_t i, j, k;
git_attr_file *file;
git_attr_rule *rule;
git_attr_assignment *assign;
git_strmap *seen = NULL;
git_dir_flag dir_flag = GIT_DIR_FLAG_UNKNOWN;
assert(repo && callback);
if (git_repository_is_bare(repo))
dir_flag = GIT_DIR_FLAG_FALSE;
if (git_attr_path__init(&path, pathname, git_repository_workdir(repo), dir_flag) < 0)
return -1;
if ((error = collect_attr_files(repo, NULL, flags, pathname, &files)) < 0 ||
(error = git_strmap_new(&seen)) < 0)
goto cleanup;
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;
if ((error = git_strmap_set(seen, assign->name, assign)) < 0)
goto cleanup;
error = callback(assign->name, assign->value, payload);
if (error) {
git_error_set_after_callback(error);
goto cleanup;
}
}
}
}
cleanup:
git_strmap_free(seen);
release_attr_files(&files);
git_attr_path__free(&path);
return error;
}
static int preload_attr_file(
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_source source,
const char *base,
const char *file,
bool allow_macros)
{
int error;
git_attr_file *preload = NULL;
if (!file)
return 0;
if (!(error = git_attr_cache__get(&preload, repo, attr_session, source, base, file,
git_attr_file__parse_buffer, allow_macros)))
git_attr_file__free(preload);
return error;
}
static int system_attr_file(
git_buf *out,
git_attr_session *attr_session)
{
int error;
if (!attr_session) {
error = git_sysdir_find_system_file(out, GIT_ATTR_FILE_SYSTEM);
if (error == GIT_ENOTFOUND)
git_error_clear();
return error;
}
if (!attr_session->init_sysdir) {
error = git_sysdir_find_system_file(&attr_session->sysdir, GIT_ATTR_FILE_SYSTEM);
if (error == GIT_ENOTFOUND)
git_error_clear();
else if (error)
return error;
attr_session->init_sysdir = 1;
}
if (attr_session->sysdir.size == 0)
return GIT_ENOTFOUND;
/* We can safely provide a git_buf with no allocation (asize == 0) to
* a consumer. This allows them to treat this as a regular `git_buf`,
* but their call to `git_buf_dispose` will not attempt to free it.
*/
git_buf_attach_notowned(
out, attr_session->sysdir.ptr, attr_session->sysdir.size);
return 0;
}
static int attr_setup(
git_repository *repo,
git_attr_session *attr_session,
uint32_t flags)
{
git_buf path = GIT_BUF_INIT;
git_index *idx = NULL;
const char *workdir;
int error = 0;
if (attr_session && attr_session->init_setup)
return 0;
if ((error = git_attr_cache__init(repo)) < 0)
return error;
/*
* Preload attribute files that could contain macros so the
* definitions will be available for later file parsing.
*/
if ((error = system_attr_file(&path, attr_session)) < 0 ||
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
NULL, path.ptr, true)) < 0) {
if (error != GIT_ENOTFOUND)
goto out;
}
if ((error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
NULL, git_repository_attr_cache(repo)->cfg_attr_file, true)) < 0)
goto out;
git_buf_clear(&path); /* git_repository_item_path expects an empty buffer, because it uses git_buf_set */
if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
path.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
if (error != GIT_ENOTFOUND)
goto out;
}
if ((workdir = git_repository_workdir(repo)) != NULL &&
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_FILE,
workdir, GIT_ATTR_FILE, true)) < 0)
goto out;
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_INDEX,
NULL, GIT_ATTR_FILE, true)) < 0)
goto out;
if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0 &&
(error = preload_attr_file(repo, attr_session, GIT_ATTR_FILE__FROM_HEAD,
NULL, GIT_ATTR_FILE, true)) < 0)
goto out;
if (attr_session)
attr_session->init_setup = 1;
out:
git_buf_dispose(&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 ((error = git_attr_cache__init(repo)) < 0)
return error;
macro = git__calloc(1, sizeof(git_attr_rule));
GIT_ERROR_CHECK_ALLOC(macro);
pool = &git_repository_attr_cache(repo)->pool;
macro->match.pattern = git_pool_strdup(pool, name);
GIT_ERROR_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, &macro->assigns, &values);
if (!error)
error = git_attr_cache__insert_macro(repo, macro);
if (error < 0)
git_attr_rule__free(macro);
return error;
}
typedef struct {
git_repository *repo;
git_attr_session *attr_session;
uint32_t flags;
const char *workdir;
git_index *index;
git_vector *files;
} attr_walk_up_info;
static int attr_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;
}
if ((flags & GIT_ATTR_CHECK_INCLUDE_HEAD) != 0)
srcs[count++] = GIT_ATTR_FILE__FROM_HEAD;
return count;
}
static int push_attr_file(
git_repository *repo,
git_attr_session *attr_session,
git_vector *list,
git_attr_file_source source,
const char *base,
const char *filename,
bool allow_macros)
{
int error = 0;
git_attr_file *file = NULL;
error = git_attr_cache__get(&file, repo, attr_session,
source, base, filename, git_attr_file__parse_buffer, allow_macros);
if (error < 0)
return error;
if (file != NULL) {
if ((error = git_vector_insert(list, file)) < 0)
git_attr_file__free(file);
}
return error;
}
static int push_one_attr(void *ref, const char *path)
{
attr_walk_up_info *info = (attr_walk_up_info *)ref;
git_attr_file_source src[GIT_ATTR_FILE_NUM_SOURCES];
int error = 0, n_src, i;
bool allow_macros;
n_src = attr_decide_sources(
info->flags, info->workdir != NULL, info->index != NULL, src);
allow_macros = info->workdir ? !strcmp(info->workdir, path) : false;
for (i = 0; !error && i < n_src; ++i)
error = push_attr_file(info->repo, info->attr_session, info->files,
src[i], path, GIT_ATTR_FILE, allow_macros);
return error;
}
static void release_attr_files(git_vector *files)
{
size_t i;
git_attr_file *file;
git_vector_foreach(files, i, file) {
git_attr_file__free(file);
files->contents[i] = NULL;
}
git_vector_free(files);
}
static int collect_attr_files(
git_repository *repo,
git_attr_session *attr_session,
uint32_t flags,
const char *path,
git_vector *files)
{
int error = 0;
git_buf dir = GIT_BUF_INIT, attrfile = GIT_BUF_INIT;
const char *workdir = git_repository_workdir(repo);
attr_walk_up_info info = { NULL };
if ((error = attr_setup(repo, attr_session, flags)) < 0)
return error;
/* 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
*/
if ((error = git_repository_item_path(&attrfile, repo, GIT_REPOSITORY_ITEM_INFO)) < 0 ||
(error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
attrfile.ptr, GIT_ATTR_FILE_INREPO, true)) < 0) {
if (error != GIT_ENOTFOUND)
goto cleanup;
}
info.repo = repo;
info.attr_session = attr_session;
info.flags = flags;
info.workdir = workdir;
if (git_repository_index__weakptr(&info.index, repo) < 0)
git_error_clear(); /* no error even if there is no index */
info.files = files;
if (!strcmp(dir.ptr, "."))
error = push_one_attr(&info, "");
else
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, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
NULL, git_repository_attr_cache(repo)->cfg_attr_file, true);
if (error < 0)
goto cleanup;
}
if ((flags & GIT_ATTR_CHECK_NO_SYSTEM) == 0) {
error = system_attr_file(&dir, attr_session);
if (!error)
error = push_attr_file(repo, attr_session, files, GIT_ATTR_FILE__FROM_FILE,
NULL, dir.ptr, true);
else if (error == GIT_ENOTFOUND)
error = 0;
}
cleanup:
if (error < 0)
release_attr_files(files);
git_buf_dispose(&attrfile);
git_buf_dispose(&dir);
return error;
}
+15
View File
@@ -0,0 +1,15 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_attr_h__
#define INCLUDE_attr_h__
#include "common.h"
#include "attr_file.h"
#include "attrcache.h"
#endif
+969
View File
@@ -0,0 +1,969 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "attr_file.h"
#include "repository.h"
#include "filebuf.h"
#include "attrcache.h"
#include "buf_text.h"
#include "git2/blob.h"
#include "git2/tree.h"
#include "blob.h"
#include "index.h"
#include "wildmatch.h"
#include <ctype.h>
static void attr_file_free(git_attr_file *file)
{
bool unlock = !git_mutex_lock(&file->lock);
git_attr_file__clear_rules(file, false);
git_pool_clear(&file->pool);
if (unlock)
git_mutex_unlock(&file->lock);
git_mutex_free(&file->lock);
git__memzero(file, sizeof(*file));
git__free(file);
}
int git_attr_file__new(
git_attr_file **out,
git_attr_file_entry *entry,
git_attr_file_source source)
{
git_attr_file *attrs = git__calloc(1, sizeof(git_attr_file));
GIT_ERROR_CHECK_ALLOC(attrs);
if (git_mutex_init(&attrs->lock) < 0) {
git_error_set(GIT_ERROR_OS, "failed to initialize lock");
git__free(attrs);
return -1;
}
git_pool_init(&attrs->pool, 1);
GIT_REFCOUNT_INC(attrs);
attrs->entry = entry;
attrs->source = source;
*out = attrs;
return 0;
}
int git_attr_file__clear_rules(git_attr_file *file, bool need_lock)
{
unsigned int i;
git_attr_rule *rule;
if (need_lock && git_mutex_lock(&file->lock) < 0) {
git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
return -1;
}
git_vector_foreach(&file->rules, i, rule)
git_attr_rule__free(rule);
git_vector_free(&file->rules);
if (need_lock)
git_mutex_unlock(&file->lock);
return 0;
}
void git_attr_file__free(git_attr_file *file)
{
if (!file)
return;
GIT_REFCOUNT_DEC(file, attr_file_free);
}
static int attr_file_oid_from_index(
git_oid *oid, git_repository *repo, const char *path)
{
int error;
git_index *idx;
size_t pos;
const git_index_entry *entry;
if ((error = git_repository_index__weakptr(&idx, repo)) < 0 ||
(error = git_index__find_pos(&pos, idx, path, 0, 0)) < 0)
return error;
if (!(entry = git_index_get_byindex(idx, pos)))
return GIT_ENOTFOUND;
*oid = entry->id;
return 0;
}
int git_attr_file__load(
git_attr_file **out,
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_entry *entry,
git_attr_file_source source,
git_attr_file_parser parser,
bool allow_macros)
{
int error = 0;
git_tree *tree = NULL;
git_tree_entry *tree_entry = NULL;
git_blob *blob = NULL;
git_buf content = GIT_BUF_INIT;
const char *content_str;
git_attr_file *file;
struct stat st;
bool nonexistent = false;
int bom_offset;
git_bom_t bom;
git_oid id;
git_object_size_t blobsize;
*out = NULL;
switch (source) {
case GIT_ATTR_FILE__IN_MEMORY:
/* in-memory attribute file doesn't need data */
break;
case GIT_ATTR_FILE__FROM_INDEX: {
if ((error = attr_file_oid_from_index(&id, repo, entry->path)) < 0 ||
(error = git_blob_lookup(&blob, repo, &id)) < 0)
return error;
/* Do not assume that data straight from the ODB is NULL-terminated;
* copy the contents of a file to a buffer to work on */
blobsize = git_blob_rawsize(blob);
GIT_ERROR_CHECK_BLOBSIZE(blobsize);
git_buf_put(&content, git_blob_rawcontent(blob), (size_t)blobsize);
break;
}
case GIT_ATTR_FILE__FROM_FILE: {
int fd = -1;
/* For open or read errors, pretend that we got ENOTFOUND. */
/* TODO: issue warning when warning API is available */
if (p_stat(entry->fullpath, &st) < 0 ||
S_ISDIR(st.st_mode) ||
(fd = git_futils_open_ro(entry->fullpath)) < 0 ||
(error = git_futils_readbuffer_fd(&content, fd, (size_t)st.st_size)) < 0)
nonexistent = true;
if (fd >= 0)
p_close(fd);
break;
}
case GIT_ATTR_FILE__FROM_HEAD: {
if ((error = git_repository_head_tree(&tree, repo)) < 0 ||
(error = git_tree_entry_bypath(&tree_entry, tree, entry->path)) < 0 ||
(error = git_blob_lookup(&blob, repo, git_tree_entry_id(tree_entry))) < 0)
goto cleanup;
/*
* Do not assume that data straight from the ODB is NULL-terminated;
* copy the contents of a file to a buffer to work on.
*/
blobsize = git_blob_rawsize(blob);
GIT_ERROR_CHECK_BLOBSIZE(blobsize);
if ((error = git_buf_put(&content,
git_blob_rawcontent(blob), (size_t)blobsize)) < 0)
goto cleanup;
break;
}
default:
git_error_set(GIT_ERROR_INVALID, "unknown file source %d", source);
return -1;
}
if ((error = git_attr_file__new(&file, entry, source)) < 0)
goto cleanup;
/* advance over a UTF8 BOM */
content_str = git_buf_cstr(&content);
bom_offset = git_buf_text_detect_bom(&bom, &content);
if (bom == GIT_BOM_UTF8)
content_str += bom_offset;
/* store the key of the attr_reader; don't bother with cache
* invalidation during the same attr reader session.
*/
if (attr_session)
file->session_key = attr_session->key;
if (parser && (error = parser(repo, file, content_str, allow_macros)) < 0) {
git_attr_file__free(file);
goto cleanup;
}
/* write cache breakers */
if (nonexistent)
file->nonexistent = 1;
else if (source == GIT_ATTR_FILE__FROM_INDEX)
git_oid_cpy(&file->cache_data.oid, git_blob_id(blob));
else if (source == GIT_ATTR_FILE__FROM_HEAD)
git_oid_cpy(&file->cache_data.oid, git_tree_id(tree));
else if (source == GIT_ATTR_FILE__FROM_FILE)
git_futils_filestamp_set_from_stat(&file->cache_data.stamp, &st);
/* else always cacheable */
*out = file;
cleanup:
git_blob_free(blob);
git_tree_entry_free(tree_entry);
git_tree_free(tree);
git_buf_dispose(&content);
return error;
}
int git_attr_file__out_of_date(
git_repository *repo,
git_attr_session *attr_session,
git_attr_file *file)
{
if (!file)
return 1;
/* we are never out of date if we just created this data in the same
* attr_session; otherwise, nonexistent files must be invalidated
*/
if (attr_session && attr_session->key == file->session_key)
return 0;
else if (file->nonexistent)
return 1;
switch (file->source) {
case GIT_ATTR_FILE__IN_MEMORY:
return 0;
case GIT_ATTR_FILE__FROM_FILE:
return git_futils_filestamp_check(
&file->cache_data.stamp, file->entry->fullpath);
case GIT_ATTR_FILE__FROM_INDEX: {
int error;
git_oid id;
if ((error = attr_file_oid_from_index(
&id, repo, file->entry->path)) < 0)
return error;
return (git_oid__cmp(&file->cache_data.oid, &id) != 0);
}
case GIT_ATTR_FILE__FROM_HEAD: {
git_tree *tree;
int error;
if ((error = git_repository_head_tree(&tree, repo)) < 0)
return error;
error = git_oid__cmp(&file->cache_data.oid, git_tree_id(tree));
git_tree_free(tree);
return error;
}
default:
git_error_set(GIT_ERROR_INVALID, "invalid file type %d", file->source);
return -1;
}
}
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);
static bool parse_optimized_patterns(
git_attr_fnmatch *spec,
git_pool *pool,
const char *pattern);
int git_attr_file__parse_buffer(
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros)
{
const char *scan = data, *context = NULL;
git_attr_rule *rule = NULL;
int error = 0;
/* If subdir file path, convert context for file paths */
if (attrs->entry && git_path_root(attrs->entry->path) < 0 &&
!git__suffixcmp(attrs->entry->path, "/" GIT_ATTR_FILE))
context = attrs->entry->path;
if (git_mutex_lock(&attrs->lock) < 0) {
git_error_set(GIT_ERROR_OS, "failed to lock attribute file");
return -1;
}
while (!error && *scan) {
/* Allocate rule if needed, otherwise re-use previous rule */
if (!rule) {
rule = git__calloc(1, sizeof(*rule));
GIT_ERROR_CHECK_ALLOC(rule);
} else
git_attr_rule__clear(rule);
rule->match.flags = GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO;
/* Parse the next "pattern attr attr attr" line */
if ((error = git_attr_fnmatch__parse(&rule->match, &attrs->pool, context, &scan)) < 0 ||
(error = git_attr_assignment__parse(repo, &attrs->pool, &rule->assigns, &scan)) < 0)
{
if (error != GIT_ENOTFOUND)
goto out;
error = 0;
continue;
}
if (rule->match.flags & GIT_ATTR_FNMATCH_MACRO) {
/* TODO: warning if macro found in file below repo root */
if (!allow_macros)
continue;
if ((error = git_attr_cache__insert_macro(repo, rule)) < 0)
goto out;
} else if ((error = git_vector_insert(&attrs->rules, rule)) < 0)
goto out;
rule = NULL;
}
out:
git_mutex_unlock(&attrs->lock);
git_attr_rule__free(rule);
return error;
}
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,
git_attr_path *path,
const char *attr,
const char **value)
{
size_t 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) {
size_t pos;
if (!git_vector_bsearch(&pos, &rule->assigns, &name)) {
*value = ((git_attr_assignment *)
git_vector_get(&rule->assigns, pos))->value;
break;
}
}
return 0;
}
int git_attr_file__load_standalone(git_attr_file **out, const char *path)
{
git_buf content = GIT_BUF_INIT;
git_attr_file *file = NULL;
int error;
if ((error = git_futils_readbuffer(&content, path)) < 0)
goto out;
/*
* Because the cache entry is allocated from the file's own pool, we
* don't have to free it - freeing file+pool will free cache entry, too.
*/
if ((error = git_attr_file__new(&file, NULL, GIT_ATTR_FILE__FROM_FILE)) < 0 ||
(error = git_attr_file__parse_buffer(NULL, file, content.ptr, true)) < 0 ||
(error = git_attr_cache__alloc_file_entry(&file->entry, NULL, path, &file->pool)) < 0)
goto out;
*out = file;
out:
if (error < 0)
git_attr_file__free(file);
git_buf_dispose(&content);
return error;
}
bool git_attr_fnmatch__match(
git_attr_fnmatch *match,
git_attr_path *path)
{
const char *relpath = path->path;
const char *filename;
int flags = 0;
/*
* If the rule was generated in a subdirectory, we must only
* use it for paths inside that directory. We can thus return
* a non-match if the prefixes don't match.
*/
if (match->containing_dir) {
if (match->flags & GIT_ATTR_FNMATCH_ICASE) {
if (git__strncasecmp(path->path, match->containing_dir, match->containing_dir_length))
return 0;
} else {
if (git__prefixcmp(path->path, match->containing_dir))
return 0;
}
relpath += match->containing_dir_length;
}
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
flags |= WM_CASEFOLD;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
filename = relpath;
flags |= WM_PATHNAME;
} else {
filename = path->basename;
}
if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
bool samename;
/*
* for attribute checks or checks at the root of this match's
* containing_dir (or root of the repository if no containing_dir),
* do not match.
*/
if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
path->basename == relpath)
return false;
/* fail match if this is a file with same name as ignored folder */
samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
!strcasecmp(match->pattern, relpath) :
!strcmp(match->pattern, relpath);
if (samename)
return false;
return (wildmatch(match->pattern, relpath, flags) == WM_MATCH);
}
return (wildmatch(match->pattern, filename, flags) == WM_MATCH);
}
bool git_attr_rule__match(
git_attr_rule *rule,
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)
{
size_t pos;
git_attr_name key;
key.name = name;
key.name_hash = git_attr_file__name_hash(name);
if (git_vector_bsearch(&pos, &rule->assigns, &key))
return NULL;
return git_vector_get(&rule->assigns, pos);
}
int git_attr_path__init(
git_attr_path *info, const char *path, const char *base, git_dir_flag dir_flag)
{
ssize_t root;
/* build full path as best we can */
git_buf_init(&info->full, 0);
if (git_path_join_unrooted(&info->full, path, base, &root) < 0)
return -1;
info->path = info->full.ptr + root;
/* 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;
switch (dir_flag)
{
case GIT_DIR_FLAG_FALSE:
info->is_dir = 0;
break;
case GIT_DIR_FLAG_TRUE:
info->is_dir = 1;
break;
case GIT_DIR_FLAG_UNKNOWN:
default:
info->is_dir = (int)git_path_isdir(info->full.ptr);
break;
}
return 0;
}
void git_attr_path__free(git_attr_path *info)
{
git_buf_dispose(&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".
*/
/*
* Determine the length of trailing spaces. Escaped spaces do not count as
* trailing whitespace.
*/
static size_t trailing_space_length(const char *p, size_t len)
{
size_t n, i;
for (n = len; n; n--) {
if (p[n-1] != ' ' && p[n-1] != '\t')
break;
/*
* Count escape-characters before space. In case where it's an
* even number of escape characters, then the escape char itself
* is escaped and the whitespace is an unescaped whitespace.
* Otherwise, the last escape char is not escaped and the
* whitespace in an escaped whitespace.
*/
i = n;
while (i > 1 && p[i-2] == '\\')
i--;
if ((n - i) % 2)
break;
}
return len - n;
}
static size_t unescape_spaces(char *str)
{
char *scan, *pos = str;
bool escaped = false;
if (!str)
return 0;
for (scan = str; *scan; scan++) {
if (!escaped && *scan == '\\') {
escaped = true;
continue;
}
/* Only insert the escape character for escaped non-spaces */
if (escaped && !git__isspace(*scan))
*pos++ = '\\';
*pos++ = *scan;
escaped = false;
}
if (pos != scan)
*pos = '\0';
return (pos - str);
}
/*
* 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 *context,
const char **base)
{
const char *pattern, *scan;
int slash_count, allow_space;
bool escaped;
assert(spec && base && *base);
if (parse_optimized_patterns(spec, pool, *base))
return 0;
spec->flags = (spec->flags & GIT_ATTR_FNMATCH__INCOMING);
allow_space = ((spec->flags & GIT_ATTR_FNMATCH_ALLOWSPACE) != 0);
pattern = *base;
while (!allow_space && git__isspace(*pattern))
pattern++;
if (!*pattern || *pattern == '#' || *pattern == '\n' ||
(*pattern == '\r' && *(pattern + 1) == '\n')) {
*base = git__next_line(pattern);
return GIT_ENOTFOUND;
}
if (*pattern == '[' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWMACRO) != 0) {
if (strncmp(pattern, "[attr]", 6) == 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_MACRO;
pattern += 6;
}
/* else a character range like [a-e]* which is accepted */
}
if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
pattern++;
}
slash_count = 0;
escaped = false;
/* Scan until a non-escaped whitespace. */
for (scan = pattern; *scan != '\0'; ++scan) {
char c = *scan;
if (c == '\\' && !escaped) {
escaped = true;
continue;
} else if (git__isspace(c) && !escaped) {
if (!allow_space || (c != ' ' && c != '\t' && c != '\r'))
break;
} else if (c == '/') {
spec->flags = spec->flags | GIT_ATTR_FNMATCH_FULLPATH;
slash_count++;
if (slash_count == 1 && pattern == scan)
pattern++;
} else if (git__iswildcard(c) && !escaped) {
/* remember if we see an unescaped wildcard in pattern */
spec->flags = spec->flags | GIT_ATTR_FNMATCH_HASWILD;
}
escaped = false;
}
*base = scan;
if ((spec->length = scan - pattern) == 0)
return GIT_ENOTFOUND;
/*
* Remove one trailing \r in case this is a CRLF delimited
* file, in the case of Icon\r\r\n, we still leave the first
* \r there to match against.
*/
if (pattern[spec->length - 1] == '\r')
if (--spec->length == 0)
return GIT_ENOTFOUND;
/* Remove trailing spaces. */
spec->length -= trailing_space_length(pattern, spec->length);
if (spec->length == 0)
return GIT_ENOTFOUND;
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 (context) {
char *slash = strrchr(context, '/');
size_t len;
if (slash) {
/* include the slash for easier matching */
len = slash - context + 1;
spec->containing_dir = git_pool_strndup(pool, context, len);
spec->containing_dir_length = len;
}
}
spec->pattern = git_pool_strndup(pool, pattern, spec->length);
if (!spec->pattern) {
*base = git__next_line(pattern);
return -1;
} else {
/* strip '\' that might have been used for internal whitespace */
spec->length = unescape_spaces(spec->pattern);
}
return 0;
}
static bool parse_optimized_patterns(
git_attr_fnmatch *spec,
git_pool *pool,
const char *pattern)
{
if (!pattern[1] && (pattern[0] == '*' || pattern[0] == '.')) {
spec->flags = GIT_ATTR_FNMATCH_MATCH_ALL;
spec->pattern = git_pool_strndup(pool, pattern, 1);
spec->length = 1;
return true;
}
return false;
}
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);
git_vector_set_cmp(assigns, 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));
GIT_ERROR_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);
GIT_ERROR_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);
GIT_ERROR_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(&macro->assigns, i, massign) {
GIT_REFCOUNT_INC(massign);
error = git_vector_insert_sorted(
assigns, massign, &merge_assignments);
if (error < 0 && error != GIT_EEXISTS) {
git_attr_assignment__free(assign);
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);
}
int git_attr_session__init(git_attr_session *session, git_repository *repo)
{
assert(repo);
memset(session, 0, sizeof(*session));
session->key = git_atomic_inc(&repo->attr_session_key);
return 0;
}
void git_attr_session__free(git_attr_session *session)
{
if (!session)
return;
git_buf_dispose(&session->sysdir);
git_buf_dispose(&session->tmp);
memset(session, 0, sizeof(git_attr_session));
}
+220
View File
@@ -0,0 +1,220 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_attr_file_h__
#define INCLUDE_attr_file_h__
#include "common.h"
#include "git2/oid.h"
#include "git2/attr.h"
#include "vector.h"
#include "pool.h"
#include "buffer.h"
#include "futils.h"
#define GIT_ATTR_FILE ".gitattributes"
#define GIT_ATTR_FILE_INREPO "attributes"
#define GIT_ATTR_FILE_SYSTEM "gitattributes"
#define GIT_ATTR_FILE_XDG "attributes"
#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)
#define GIT_ATTR_FNMATCH_ICASE (1U << 7)
#define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8)
#define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9)
#define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10)
#define GIT_ATTR_FNMATCH__INCOMING \
(GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO)
typedef enum {
GIT_ATTR_FILE__IN_MEMORY = 0,
GIT_ATTR_FILE__FROM_FILE = 1,
GIT_ATTR_FILE__FROM_INDEX = 2,
GIT_ATTR_FILE__FROM_HEAD = 3,
GIT_ATTR_FILE_NUM_SOURCES = 4
} git_attr_file_source;
extern const char *git_attr__true;
extern const char *git_attr__false;
extern const char *git_attr__unset;
typedef struct {
char *pattern;
size_t length;
char *containing_dir;
size_t containing_dir_length;
unsigned int flags;
} git_attr_fnmatch;
typedef struct {
git_attr_fnmatch match;
git_vector assigns; /* vector of <git_attr_assignment*> */
} 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_attr_file_entry git_attr_file_entry;
typedef struct {
git_refcount rc;
git_mutex lock;
git_attr_file_entry *entry;
git_attr_file_source source;
git_vector rules; /* vector of <rule*> or <fnmatch*> */
git_pool pool;
unsigned int nonexistent:1;
int session_key;
union {
git_oid oid;
git_futils_filestamp stamp;
} cache_data;
} git_attr_file;
struct git_attr_file_entry {
git_attr_file *file[GIT_ATTR_FILE_NUM_SOURCES];
const char *path; /* points into fullpath */
char fullpath[GIT_FLEX_ARRAY];
};
typedef struct {
git_buf full;
char *path;
char *basename;
int is_dir;
} git_attr_path;
/* A git_attr_session can provide an "instance" of reading, to prevent cache
* invalidation during a single operation instance (like checkout).
*/
typedef struct {
int key;
unsigned int init_setup:1,
init_sysdir:1;
git_buf sysdir;
git_buf tmp;
} git_attr_session;
extern int git_attr_session__init(git_attr_session *attr_session, git_repository *repo);
extern void git_attr_session__free(git_attr_session *session);
extern int git_attr_get_many_with_session(
const char **values_out,
git_repository *repo,
git_attr_session *attr_session,
uint32_t flags,
const char *path,
size_t num_attr,
const char **names);
typedef int (*git_attr_file_parser)(
git_repository *repo,
git_attr_file *file,
const char *data,
bool allow_macros);
/*
* git_attr_file API
*/
int git_attr_file__new(
git_attr_file **out,
git_attr_file_entry *entry,
git_attr_file_source source);
void git_attr_file__free(git_attr_file *file);
int git_attr_file__load(
git_attr_file **out,
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_entry *ce,
git_attr_file_source source,
git_attr_file_parser parser,
bool allow_macros);
int git_attr_file__load_standalone(
git_attr_file **out, const char *path);
int git_attr_file__out_of_date(
git_repository *repo, git_attr_session *session, git_attr_file *file);
int git_attr_file__parse_buffer(
git_repository *repo, git_attr_file *attrs, const char *data, bool allow_macros);
int git_attr_file__clear_rules(
git_attr_file *file, bool need_lock);
int git_attr_file__lookup_one(
git_attr_file *file,
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)))
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,
git_attr_path *path);
extern void git_attr_rule__free(git_attr_rule *rule);
extern bool git_attr_rule__match(
git_attr_rule *rule,
git_attr_path *path);
extern git_attr_assignment *git_attr_rule__lookup_assignment(
git_attr_rule *rule, const char *name);
typedef enum { GIT_DIR_FLAG_TRUE = 1, GIT_DIR_FLAG_FALSE = 0, GIT_DIR_FLAG_UNKNOWN = -1 } git_dir_flag;
extern int git_attr_path__init(
git_attr_path *info, const char *path, const char *base, git_dir_flag is_dir);
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
+467
View File
@@ -0,0 +1,467 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "attrcache.h"
#include "repository.h"
#include "attr_file.h"
#include "config.h"
#include "sysdir.h"
#include "ignore.h"
GIT_INLINE(int) attr_cache_lock(git_attr_cache *cache)
{
GIT_UNUSED(cache); /* avoid warning if threading is off */
if (git_mutex_lock(&cache->lock) < 0) {
git_error_set(GIT_ERROR_OS, "unable to get attr cache lock");
return -1;
}
return 0;
}
GIT_INLINE(void) attr_cache_unlock(git_attr_cache *cache)
{
GIT_UNUSED(cache); /* avoid warning if threading is off */
git_mutex_unlock(&cache->lock);
}
GIT_INLINE(git_attr_file_entry *) attr_cache_lookup_entry(
git_attr_cache *cache, const char *path)
{
return git_strmap_get(cache->files, path);
}
int git_attr_cache__alloc_file_entry(
git_attr_file_entry **out,
const char *base,
const char *path,
git_pool *pool)
{
size_t baselen = 0, pathlen = strlen(path);
size_t cachesize = sizeof(git_attr_file_entry) + pathlen + 1;
git_attr_file_entry *ce;
if (base != NULL && git_path_root(path) < 0) {
baselen = strlen(base);
cachesize += baselen;
if (baselen && base[baselen - 1] != '/')
cachesize++;
}
ce = git_pool_mallocz(pool, cachesize);
GIT_ERROR_CHECK_ALLOC(ce);
if (baselen) {
memcpy(ce->fullpath, base, baselen);
if (base[baselen - 1] != '/')
ce->fullpath[baselen++] = '/';
}
memcpy(&ce->fullpath[baselen], path, pathlen);
ce->path = &ce->fullpath[baselen];
*out = ce;
return 0;
}
/* call with attrcache locked */
static int attr_cache_make_entry(
git_attr_file_entry **out, git_repository *repo, const char *path)
{
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file_entry *entry = NULL;
int error;
if ((error = git_attr_cache__alloc_file_entry(&entry, git_repository_workdir(repo),
path, &cache->pool)) < 0)
return error;
if ((error = git_strmap_set(cache->files, entry->path, entry)) < 0)
return error;
*out = entry;
return error;
}
/* insert entry or replace existing if we raced with another thread */
static int attr_cache_upsert(git_attr_cache *cache, git_attr_file *file)
{
git_attr_file_entry *entry;
git_attr_file *old;
if (attr_cache_lock(cache) < 0)
return -1;
entry = attr_cache_lookup_entry(cache, file->entry->path);
GIT_REFCOUNT_OWN(file, entry);
GIT_REFCOUNT_INC(file);
/*
* Replace the existing value if another thread has
* created it in the meantime.
*/
old = git__swap(entry->file[file->source], file);
if (old) {
GIT_REFCOUNT_OWN(old, NULL);
git_attr_file__free(old);
}
attr_cache_unlock(cache);
return 0;
}
static int attr_cache_remove(git_attr_cache *cache, git_attr_file *file)
{
int error = 0;
git_attr_file_entry *entry;
git_attr_file *old = NULL;
if (!file)
return 0;
if ((error = attr_cache_lock(cache)) < 0)
return error;
if ((entry = attr_cache_lookup_entry(cache, file->entry->path)) != NULL)
old = git__compare_and_swap(&entry->file[file->source], file, NULL);
attr_cache_unlock(cache);
if (old) {
GIT_REFCOUNT_OWN(old, NULL);
git_attr_file__free(old);
}
return error;
}
/* Look up cache entry and file.
* - If entry is not present, create it while the cache is locked.
* - If file is present, increment refcount before returning it, so the
* cache can be unlocked and it won't go away.
*/
static int attr_cache_lookup(
git_attr_file **out_file,
git_attr_file_entry **out_entry,
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_source source,
const char *base,
const char *filename)
{
int error = 0;
git_buf path = GIT_BUF_INIT;
const char *wd = git_repository_workdir(repo), *relfile;
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file_entry *entry = NULL;
git_attr_file *file = NULL;
/* join base and path as needed */
if (base != NULL && git_path_root(filename) < 0) {
git_buf *p = attr_session ? &attr_session->tmp : &path;
if (git_buf_joinpath(p, base, filename) < 0)
return -1;
filename = p->ptr;
}
relfile = filename;
if (wd && !git__prefixcmp(relfile, wd))
relfile += strlen(wd);
/* check cache for existing entry */
if ((error = attr_cache_lock(cache)) < 0)
goto cleanup;
entry = attr_cache_lookup_entry(cache, relfile);
if (!entry)
error = attr_cache_make_entry(&entry, repo, relfile);
else if (entry->file[source] != NULL) {
file = entry->file[source];
GIT_REFCOUNT_INC(file);
}
attr_cache_unlock(cache);
cleanup:
*out_file = file;
*out_entry = entry;
git_buf_dispose(&path);
return error;
}
int git_attr_cache__get(
git_attr_file **out,
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_source source,
const char *base,
const char *filename,
git_attr_file_parser parser,
bool allow_macros)
{
int error = 0;
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file_entry *entry = NULL;
git_attr_file *file = NULL, *updated = NULL;
if ((error = attr_cache_lookup(
&file, &entry, repo, attr_session, source, base, filename)) < 0)
return error;
/* load file if we don't have one or if existing one is out of date */
if (!file || (error = git_attr_file__out_of_date(repo, attr_session, file)) > 0)
error = git_attr_file__load(&updated, repo, attr_session, entry, source, parser, allow_macros);
/* if we loaded the file, insert into and/or update cache */
if (updated) {
if ((error = attr_cache_upsert(cache, updated)) < 0)
git_attr_file__free(updated);
else {
git_attr_file__free(file); /* offset incref from lookup */
file = updated;
}
}
/* if file could not be loaded */
if (error < 0) {
/* remove existing entry */
if (file) {
attr_cache_remove(cache, file);
git_attr_file__free(file); /* offset incref from lookup */
file = NULL;
}
/* no error if file simply doesn't exist */
if (error == GIT_ENOTFOUND) {
git_error_clear();
error = 0;
}
}
*out = file;
return error;
}
bool git_attr_cache__is_cached(
git_repository *repo,
git_attr_file_source source,
const char *filename)
{
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_file_entry *entry;
git_strmap *files;
if (!cache || !(files = cache->files))
return false;
if ((entry = git_strmap_get(files, filename)) == NULL)
return false;
return entry && (entry->file[source] != NULL);
}
static int attr_cache__lookup_path(
char **out, git_config *cfg, const char *key, const char *fallback)
{
git_buf buf = GIT_BUF_INIT;
int error;
git_config_entry *entry = NULL;
*out = NULL;
if ((error = git_config__lookup_entry(&entry, cfg, key, false)) < 0)
return error;
if (entry) {
const char *cfgval = entry->value;
/* expand leading ~/ as needed */
if (cfgval && cfgval[0] == '~' && cfgval[1] == '/') {
if (! (error = git_sysdir_expand_global_file(&buf, &cfgval[2])))
*out = git_buf_detach(&buf);
} else if (cfgval) {
*out = git__strdup(cfgval);
}
}
else if (!git_sysdir_find_xdg_file(&buf, fallback)) {
*out = git_buf_detach(&buf);
}
git_config_entry_free(entry);
git_buf_dispose(&buf);
return error;
}
static void attr_cache__free(git_attr_cache *cache)
{
bool unlock;
if (!cache)
return;
unlock = (attr_cache_lock(cache) == 0);
if (cache->files != NULL) {
git_attr_file_entry *entry;
git_attr_file *file;
int i;
git_strmap_foreach_value(cache->files, entry, {
for (i = 0; i < GIT_ATTR_FILE_NUM_SOURCES; ++i) {
if ((file = git__swap(entry->file[i], NULL)) != NULL) {
GIT_REFCOUNT_OWN(file, NULL);
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);
git__free(cache->cfg_attr_file);
cache->cfg_attr_file = NULL;
git__free(cache->cfg_excl_file);
cache->cfg_excl_file = NULL;
if (unlock)
attr_cache_unlock(cache);
git_mutex_free(&cache->lock);
git__free(cache);
}
int git_attr_cache__init(git_repository *repo)
{
int ret = 0;
git_attr_cache *cache = git_repository_attr_cache(repo);
git_config *cfg = NULL;
if (cache)
return 0;
cache = git__calloc(1, sizeof(git_attr_cache));
GIT_ERROR_CHECK_ALLOC(cache);
/* set up lock */
if (git_mutex_init(&cache->lock) < 0) {
git_error_set(GIT_ERROR_OS, "unable to initialize lock for attr cache");
git__free(cache);
return -1;
}
if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0)
goto cancel;
/* cache config settings for attributes and ignores */
ret = attr_cache__lookup_path(
&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG);
if (ret < 0)
goto cancel;
ret = attr_cache__lookup_path(
&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG, GIT_IGNORE_FILE_XDG);
if (ret < 0)
goto cancel;
/* allocate hashtable for attribute and ignore file contents,
* hashtable for attribute macros, and string pool
*/
if ((ret = git_strmap_new(&cache->files)) < 0 ||
(ret = git_strmap_new(&cache->macros)) < 0)
goto cancel;
git_pool_init(&cache->pool, 1);
cache = git__compare_and_swap(&repo->attrcache, NULL, cache);
if (cache)
goto cancel; /* raced with another thread, free this but no error */
git_config_free(cfg);
/* insert default macros */
return git_attr_add_macro(repo, "binary", "-diff -merge -text -crlf");
cancel:
attr_cache__free(cache);
git_config_free(cfg);
return ret;
}
void git_attr_cache_flush(git_repository *repo)
{
git_attr_cache *cache;
/* this could be done less expensively, but for now, we'll just free
* the entire attrcache and let the next use reinitialize it...
*/
if (repo && (cache = git__swap(repo->attrcache, NULL)) != NULL)
attr_cache__free(cache);
}
int git_attr_cache__insert_macro(git_repository *repo, git_attr_rule *macro)
{
git_attr_cache *cache = git_repository_attr_cache(repo);
git_attr_rule *preexisting;
bool locked = false;
int error = 0;
/*
* Callers assume that if we return success, that the
* macro will have been adopted by the attributes cache.
* Thus, we have to free the macro here if it's not being
* added to the cache.
*
* TODO: generate warning log if (macro->assigns.length == 0)
*/
if (macro->assigns.length == 0) {
git_attr_rule__free(macro);
goto out;
}
if ((error = attr_cache_lock(cache)) < 0)
goto out;
locked = true;
if ((preexisting = git_strmap_get(cache->macros, macro->match.pattern)) != NULL)
git_attr_rule__free(preexisting);
if ((error = git_strmap_set(cache->macros, macro->match.pattern, macro)) < 0)
goto out;
out:
if (locked)
attr_cache_unlock(cache);
return error;
}
git_attr_rule *git_attr_cache__lookup_macro(
git_repository *repo, const char *name)
{
git_strmap *macros = git_repository_attr_cache(repo)->macros;
return git_strmap_get(macros, name);
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_attrcache_h__
#define INCLUDE_attrcache_h__
#include "common.h"
#include "attr_file.h"
#include "strmap.h"
#define GIT_ATTR_CONFIG "core.attributesfile"
#define GIT_IGNORE_CONFIG "core.excludesfile"
typedef struct {
char *cfg_attr_file; /* cached value of core.attributesfile */
char *cfg_excl_file; /* cached value of core.excludesfile */
git_strmap *files; /* hash path to git_attr_cache_entry records */
git_strmap *macros; /* hash name to vector<git_attr_assignment> */
git_mutex lock;
git_pool pool;
} git_attr_cache;
extern int git_attr_cache__init(git_repository *repo);
/* get file - loading and reload as needed */
extern int git_attr_cache__get(
git_attr_file **file,
git_repository *repo,
git_attr_session *attr_session,
git_attr_file_source source,
const char *base,
const char *filename,
git_attr_file_parser parser,
bool allow_macros);
extern bool git_attr_cache__is_cached(
git_repository *repo,
git_attr_file_source source,
const char *path);
extern int git_attr_cache__alloc_file_entry(
git_attr_file_entry **out,
const char *base,
const char *path,
git_pool *pool);
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);
#endif
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_bitvec_h__
#define INCLUDE_bitvec_h__
#include "common.h"
/*
* This is a silly little fixed length bit vector type that will store
* vectors of 64 bits or less directly in the structure and allocate
* memory for vectors longer than 64 bits. You can use the two versions
* transparently through the API and avoid heap allocation completely when
* using a short bit vector as a result.
*/
typedef struct {
size_t length;
union {
uint64_t *words;
uint64_t bits;
} u;
} git_bitvec;
GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity)
{
memset(bv, 0x0, sizeof(*bv));
if (capacity >= 64) {
bv->length = (capacity / 64) + 1;
bv->u.words = git__calloc(bv->length, sizeof(uint64_t));
if (!bv->u.words)
return -1;
}
return 0;
}
#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64))
#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits)
GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on)
{
uint64_t *word = GIT_BITVEC_WORD(bv, bit);
uint64_t mask = GIT_BITVEC_MASK(bit);
if (on)
*word |= mask;
else
*word &= ~mask;
}
GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit)
{
uint64_t *word = GIT_BITVEC_WORD(bv, bit);
return (*word & GIT_BITVEC_MASK(bit)) != 0;
}
GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv)
{
if (!bv->length)
bv->u.bits = 0;
else
memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t));
}
GIT_INLINE(void) git_bitvec_free(git_bitvec *bv)
{
if (bv->length)
git__free(bv->u.words);
}
#endif
+544
View File
@@ -0,0 +1,544 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "blame.h"
#include "git2/commit.h"
#include "git2/revparse.h"
#include "git2/revwalk.h"
#include "git2/tree.h"
#include "git2/diff.h"
#include "git2/blob.h"
#include "git2/signature.h"
#include "git2/mailmap.h"
#include "util.h"
#include "repository.h"
#include "blame_git.h"
static int hunk_byfinalline_search_cmp(const void *key, const void *entry)
{
git_blame_hunk *hunk = (git_blame_hunk*)entry;
size_t lineno = *(size_t*)key;
size_t lines_in_hunk = hunk->lines_in_hunk;
size_t final_start_line_number = hunk->final_start_line_number;
if (lineno < final_start_line_number)
return -1;
if (lineno >= final_start_line_number + lines_in_hunk)
return 1;
return 0;
}
static int paths_cmp(const void *a, const void *b) { return git__strcmp((char*)a, (char*)b); }
static int hunk_cmp(const void *_a, const void *_b)
{
git_blame_hunk *a = (git_blame_hunk*)_a,
*b = (git_blame_hunk*)_b;
if (a->final_start_line_number > b->final_start_line_number)
return 1;
else if (a->final_start_line_number < b->final_start_line_number)
return -1;
else
return 0;
}
static bool hunk_ends_at_or_before_line(git_blame_hunk *hunk, size_t line)
{
return line >= (hunk->final_start_line_number + hunk->lines_in_hunk - 1);
}
static bool hunk_starts_at_or_after_line(git_blame_hunk *hunk, size_t line)
{
return line <= hunk->final_start_line_number;
}
static git_blame_hunk* new_hunk(
size_t start,
size_t lines,
size_t orig_start,
const char *path)
{
git_blame_hunk *hunk = git__calloc(1, sizeof(git_blame_hunk));
if (!hunk) return NULL;
hunk->lines_in_hunk = lines;
hunk->final_start_line_number = start;
hunk->orig_start_line_number = orig_start;
hunk->orig_path = path ? git__strdup(path) : NULL;
return hunk;
}
static git_blame_hunk* dup_hunk(git_blame_hunk *hunk)
{
git_blame_hunk *newhunk = new_hunk(
hunk->final_start_line_number,
hunk->lines_in_hunk,
hunk->orig_start_line_number,
hunk->orig_path);
if (!newhunk)
return NULL;
git_oid_cpy(&newhunk->orig_commit_id, &hunk->orig_commit_id);
git_oid_cpy(&newhunk->final_commit_id, &hunk->final_commit_id);
newhunk->boundary = hunk->boundary;
git_signature_dup(&newhunk->final_signature, hunk->final_signature);
git_signature_dup(&newhunk->orig_signature, hunk->orig_signature);
return newhunk;
}
static void free_hunk(git_blame_hunk *hunk)
{
git__free((void*)hunk->orig_path);
git_signature_free(hunk->final_signature);
git_signature_free(hunk->orig_signature);
git__free(hunk);
}
/* Starting with the hunk that includes start_line, shift all following hunks'
* final_start_line by shift_by lines */
static void shift_hunks_by(git_vector *v, size_t start_line, int shift_by)
{
size_t i;
if (!git_vector_bsearch2(&i, v, hunk_byfinalline_search_cmp, &start_line)) {
for (; i < v->length; i++) {
git_blame_hunk *hunk = (git_blame_hunk*)v->contents[i];
hunk->final_start_line_number += shift_by;
}
}
}
git_blame* git_blame__alloc(
git_repository *repo,
git_blame_options opts,
const char *path)
{
git_blame *gbr = git__calloc(1, sizeof(git_blame));
if (!gbr)
return NULL;
gbr->repository = repo;
gbr->options = opts;
if (git_vector_init(&gbr->hunks, 8, hunk_cmp) < 0 ||
git_vector_init(&gbr->paths, 8, paths_cmp) < 0 ||
(gbr->path = git__strdup(path)) == NULL ||
git_vector_insert(&gbr->paths, git__strdup(path)) < 0)
{
git_blame_free(gbr);
return NULL;
}
if (opts.flags & GIT_BLAME_USE_MAILMAP &&
git_mailmap_from_repository(&gbr->mailmap, repo) < 0) {
git_blame_free(gbr);
return NULL;
}
return gbr;
}
void git_blame_free(git_blame *blame)
{
size_t i;
git_blame_hunk *hunk;
if (!blame) return;
git_vector_foreach(&blame->hunks, i, hunk)
free_hunk(hunk);
git_vector_free(&blame->hunks);
git_vector_free_deep(&blame->paths);
git_array_clear(blame->line_index);
git_mailmap_free(blame->mailmap);
git__free(blame->path);
git_blob_free(blame->final_blob);
git__free(blame);
}
uint32_t git_blame_get_hunk_count(git_blame *blame)
{
assert(blame);
return (uint32_t)blame->hunks.length;
}
const git_blame_hunk *git_blame_get_hunk_byindex(git_blame *blame, uint32_t index)
{
assert(blame);
return (git_blame_hunk*)git_vector_get(&blame->hunks, index);
}
const git_blame_hunk *git_blame_get_hunk_byline(git_blame *blame, size_t lineno)
{
size_t i, new_lineno = lineno;
assert(blame);
if (!git_vector_bsearch2(&i, &blame->hunks, hunk_byfinalline_search_cmp, &new_lineno)) {
return git_blame_get_hunk_byindex(blame, (uint32_t)i);
}
return NULL;
}
static int normalize_options(
git_blame_options *out,
const git_blame_options *in,
git_repository *repo)
{
git_blame_options dummy = GIT_BLAME_OPTIONS_INIT;
if (!in) in = &dummy;
memcpy(out, in, sizeof(git_blame_options));
/* No newest_commit => HEAD */
if (git_oid_is_zero(&out->newest_commit)) {
if (git_reference_name_to_id(&out->newest_commit, repo, "HEAD") < 0) {
return -1;
}
}
/* min_line 0 really means 1 */
if (!out->min_line) out->min_line = 1;
/* max_line 0 really means N, but we don't know N yet */
/* Fix up option implications */
if (out->flags & GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES;
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES;
if (out->flags & GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES)
out->flags |= GIT_BLAME_TRACK_COPIES_SAME_FILE;
return 0;
}
static git_blame_hunk *split_hunk_in_vector(
git_vector *vec,
git_blame_hunk *hunk,
size_t rel_line,
bool return_new)
{
size_t new_line_count;
git_blame_hunk *nh;
/* Don't split if already at a boundary */
if (rel_line <= 0 ||
rel_line >= hunk->lines_in_hunk)
{
return hunk;
}
new_line_count = hunk->lines_in_hunk - rel_line;
nh = new_hunk(hunk->final_start_line_number + rel_line, new_line_count,
hunk->orig_start_line_number + rel_line, hunk->orig_path);
if (!nh)
return NULL;
git_oid_cpy(&nh->final_commit_id, &hunk->final_commit_id);
git_oid_cpy(&nh->orig_commit_id, &hunk->orig_commit_id);
/* Adjust hunk that was split */
hunk->lines_in_hunk -= new_line_count;
git_vector_insert_sorted(vec, nh, NULL);
{
git_blame_hunk *ret = return_new ? nh : hunk;
return ret;
}
}
/*
* Construct a list of char indices for where lines begin
* Adapted from core git:
* https://github.com/gitster/git/blob/be5c9fb9049ed470e7005f159bb923a5f4de1309/builtin/blame.c#L1760-L1789
*/
static int index_blob_lines(git_blame *blame)
{
const char *buf = blame->final_buf;
size_t len = blame->final_buf_size;
int num = 0, incomplete = 0, bol = 1;
size_t *i;
if (len && buf[len-1] != '\n')
incomplete++; /* incomplete line at the end */
while (len--) {
if (bol) {
i = git_array_alloc(blame->line_index);
GIT_ERROR_CHECK_ALLOC(i);
*i = buf - blame->final_buf;
bol = 0;
}
if (*buf++ == '\n') {
num++;
bol = 1;
}
}
i = git_array_alloc(blame->line_index);
GIT_ERROR_CHECK_ALLOC(i);
*i = buf - blame->final_buf;
blame->num_lines = num + incomplete;
return blame->num_lines;
}
static git_blame_hunk* hunk_from_entry(git_blame__entry *e, git_blame *blame)
{
git_blame_hunk *h = new_hunk(
e->lno+1, e->num_lines, e->s_lno+1, e->suspect->path);
if (!h)
return NULL;
git_oid_cpy(&h->final_commit_id, git_commit_id(e->suspect->commit));
git_oid_cpy(&h->orig_commit_id, git_commit_id(e->suspect->commit));
git_commit_author_with_mailmap(
&h->final_signature, e->suspect->commit, blame->mailmap);
git_signature_dup(&h->orig_signature, h->final_signature);
h->boundary = e->is_boundary ? 1 : 0;
return h;
}
static int load_blob(git_blame *blame)
{
int error;
if (blame->final_blob) return 0;
error = git_commit_lookup(&blame->final, blame->repository, &blame->options.newest_commit);
if (error < 0)
goto cleanup;
error = git_object_lookup_bypath((git_object**)&blame->final_blob,
(git_object*)blame->final, blame->path, GIT_OBJECT_BLOB);
cleanup:
return error;
}
static int blame_internal(git_blame *blame)
{
int error;
git_blame__entry *ent = NULL;
git_blame__origin *o;
if ((error = load_blob(blame)) < 0 ||
(error = git_blame__get_origin(&o, blame, blame->final, blame->path)) < 0)
goto cleanup;
if (git_blob_rawsize(blame->final_blob) > SIZE_MAX) {
git_error_set(GIT_ERROR_NOMEMORY, "blob is too large to blame");
error = -1;
goto cleanup;
}
blame->final_buf = git_blob_rawcontent(blame->final_blob);
blame->final_buf_size = (size_t)git_blob_rawsize(blame->final_blob);
ent = git__calloc(1, sizeof(git_blame__entry));
GIT_ERROR_CHECK_ALLOC(ent);
ent->num_lines = index_blob_lines(blame);
ent->lno = blame->options.min_line - 1;
ent->num_lines = ent->num_lines - blame->options.min_line + 1;
if (blame->options.max_line > 0)
ent->num_lines = blame->options.max_line - blame->options.min_line + 1;
ent->s_lno = ent->lno;
ent->suspect = o;
blame->ent = ent;
error = git_blame__like_git(blame, blame->options.flags);
cleanup:
for (ent = blame->ent; ent; ) {
git_blame__entry *e = ent->next;
git_blame_hunk *h = hunk_from_entry(ent, blame);
git_vector_insert(&blame->hunks, h);
git_blame__free_entry(ent);
ent = e;
}
return error;
}
/*******************************************************************************
* File blaming
******************************************************************************/
int git_blame_file(
git_blame **out,
git_repository *repo,
const char *path,
git_blame_options *options)
{
int error = -1;
git_blame_options normOptions = GIT_BLAME_OPTIONS_INIT;
git_blame *blame = NULL;
assert(out && repo && path);
if ((error = normalize_options(&normOptions, options, repo)) < 0)
goto on_error;
blame = git_blame__alloc(repo, normOptions, path);
GIT_ERROR_CHECK_ALLOC(blame);
if ((error = load_blob(blame)) < 0)
goto on_error;
if ((error = blame_internal(blame)) < 0)
goto on_error;
*out = blame;
return 0;
on_error:
git_blame_free(blame);
return error;
}
/*******************************************************************************
* Buffer blaming
*******************************************************************************/
static bool hunk_is_bufferblame(git_blame_hunk *hunk)
{
return git_oid_is_zero(&hunk->final_commit_id);
}
static int buffer_hunk_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
void *payload)
{
git_blame *blame = (git_blame*)payload;
uint32_t wedge_line;
GIT_UNUSED(delta);
wedge_line = (hunk->old_lines == 0) ? hunk->new_start : hunk->old_start;
blame->current_diff_line = wedge_line;
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byline(blame, wedge_line);
if (!blame->current_hunk) {
/* Line added at the end of the file */
blame->current_hunk = new_hunk(wedge_line, 0, wedge_line, blame->path);
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
git_vector_insert(&blame->hunks, blame->current_hunk);
} else if (!hunk_starts_at_or_after_line(blame->current_hunk, wedge_line)){
/* If this hunk doesn't start between existing hunks, split a hunk up so it does */
blame->current_hunk = split_hunk_in_vector(&blame->hunks, blame->current_hunk,
wedge_line - blame->current_hunk->orig_start_line_number, true);
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
}
return 0;
}
static int ptrs_equal_cmp(const void *a, const void *b) { return a<b ? -1 : a>b ? 1 : 0; }
static int buffer_line_cb(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
git_blame *blame = (git_blame*)payload;
GIT_UNUSED(delta);
GIT_UNUSED(hunk);
GIT_UNUSED(line);
if (line->origin == GIT_DIFF_LINE_ADDITION) {
if (hunk_is_bufferblame(blame->current_hunk) &&
hunk_ends_at_or_before_line(blame->current_hunk, blame->current_diff_line)) {
/* Append to the current buffer-blame hunk */
blame->current_hunk->lines_in_hunk++;
shift_hunks_by(&blame->hunks, blame->current_diff_line+1, 1);
} else {
/* Create a new buffer-blame hunk with this line */
shift_hunks_by(&blame->hunks, blame->current_diff_line, 1);
blame->current_hunk = new_hunk(blame->current_diff_line, 1, 0, blame->path);
GIT_ERROR_CHECK_ALLOC(blame->current_hunk);
git_vector_insert_sorted(&blame->hunks, blame->current_hunk, NULL);
}
blame->current_diff_line++;
}
if (line->origin == GIT_DIFF_LINE_DELETION) {
/* Trim the line from the current hunk; remove it if it's now empty */
size_t shift_base = blame->current_diff_line + blame->current_hunk->lines_in_hunk+1;
if (--(blame->current_hunk->lines_in_hunk) == 0) {
size_t i;
shift_base--;
if (!git_vector_search2(&i, &blame->hunks, ptrs_equal_cmp, blame->current_hunk)) {
git_vector_remove(&blame->hunks, i);
free_hunk(blame->current_hunk);
blame->current_hunk = (git_blame_hunk*)git_blame_get_hunk_byindex(blame, (uint32_t)i);
}
}
shift_hunks_by(&blame->hunks, shift_base, -1);
}
return 0;
}
int git_blame_buffer(
git_blame **out,
git_blame *reference,
const char *buffer,
size_t buffer_len)
{
git_blame *blame;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
size_t i;
git_blame_hunk *hunk;
diffopts.context_lines = 0;
assert(out && reference && buffer && buffer_len);
blame = git_blame__alloc(reference->repository, reference->options, reference->path);
GIT_ERROR_CHECK_ALLOC(blame);
/* Duplicate all of the hunk structures in the reference blame */
git_vector_foreach(&reference->hunks, i, hunk) {
git_blame_hunk *h = dup_hunk(hunk);
GIT_ERROR_CHECK_ALLOC(h);
git_vector_insert(&blame->hunks, h);
}
/* Diff to the reference blob */
git_diff_blob_to_buffer(reference->final_blob, blame->path,
buffer, buffer_len, blame->path, &diffopts,
NULL, NULL, buffer_hunk_cb, buffer_line_cb, blame);
*out = blame;
return 0;
}
int git_blame_options_init(git_blame_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_blame_options, GIT_BLAME_OPTIONS_INIT);
return 0;
}
int git_blame_init_options(git_blame_options *opts, unsigned int version)
{
return git_blame_options_init(opts, version);
}
+95
View File
@@ -0,0 +1,95 @@
#ifndef INCLUDE_blame_h__
#define INCLUDE_blame_h__
#include "common.h"
#include "git2/blame.h"
#include "vector.h"
#include "diff.h"
#include "array.h"
#include "git2/oid.h"
/*
* One blob in a commit that is being suspected
*/
typedef struct git_blame__origin {
int refcnt;
struct git_blame__origin *previous;
git_commit *commit;
git_blob *blob;
char path[GIT_FLEX_ARRAY];
} git_blame__origin;
/*
* Each group of lines is described by a git_blame__entry; it can be split
* as we pass blame to the parents. They form a linked list in the
* scoreboard structure, sorted by the target line number.
*/
typedef struct git_blame__entry {
struct git_blame__entry *prev;
struct git_blame__entry *next;
/* the first line of this group in the final image;
* internally all line numbers are 0 based.
*/
size_t lno;
/* how many lines this group has */
size_t num_lines;
/* the commit that introduced this group into the final image */
git_blame__origin *suspect;
/* true if the suspect is truly guilty; false while we have not
* checked if the group came from one of its parents.
*/
bool guilty;
/* true if the entry has been scanned for copies in the current parent
*/
bool scanned;
/* the line number of the first line of this group in the
* suspect's file; internally all line numbers are 0 based.
*/
size_t s_lno;
/* how significant this entry is -- cached to avoid
* scanning the lines over and over.
*/
unsigned score;
/* Whether this entry has been tracked to a boundary commit.
*/
bool is_boundary;
} git_blame__entry;
struct git_blame {
char *path;
git_repository *repository;
git_mailmap *mailmap;
git_blame_options options;
git_vector hunks;
git_vector paths;
git_blob *final_blob;
git_array_t(size_t) line_index;
size_t current_diff_line;
git_blame_hunk *current_hunk;
/* Scoreboard fields */
git_commit *final;
git_blame__entry *ent;
int num_lines;
const char *final_buf;
size_t final_buf_size;
};
git_blame *git_blame__alloc(
git_repository *repo,
git_blame_options opts,
const char *path);
#endif
+682
View File
@@ -0,0 +1,682 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "blame_git.h"
#include "commit.h"
#include "blob.h"
#include "xdiff/xinclude.h"
#include "diff_xdiff.h"
/*
* Origin is refcounted and usually we keep the blob contents to be
* reused.
*/
static git_blame__origin *origin_incref(git_blame__origin *o)
{
if (o)
o->refcnt++;
return o;
}
static void origin_decref(git_blame__origin *o)
{
if (o && --o->refcnt <= 0) {
if (o->previous)
origin_decref(o->previous);
git_blob_free(o->blob);
git_commit_free(o->commit);
git__free(o);
}
}
/* Given a commit and a path in it, create a new origin structure. */
static int make_origin(git_blame__origin **out, git_commit *commit, const char *path)
{
git_blame__origin *o;
git_object *blob;
size_t path_len = strlen(path), alloc_len;
int error = 0;
if ((error = git_object_lookup_bypath(&blob, (git_object*)commit,
path, GIT_OBJECT_BLOB)) < 0)
return error;
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, sizeof(*o), path_len);
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 1);
o = git__calloc(1, alloc_len);
GIT_ERROR_CHECK_ALLOC(o);
o->commit = commit;
o->blob = (git_blob *) blob;
o->refcnt = 1;
strcpy(o->path, path);
*out = o;
return 0;
}
/* Locate an existing origin or create a new one. */
int git_blame__get_origin(
git_blame__origin **out,
git_blame *blame,
git_commit *commit,
const char *path)
{
git_blame__entry *e;
for (e = blame->ent; e; e = e->next) {
if (e->suspect->commit == commit && !strcmp(e->suspect->path, path)) {
*out = origin_incref(e->suspect);
}
}
return make_origin(out, commit, path);
}
typedef struct blame_chunk_cb_data {
git_blame *blame;
git_blame__origin *target;
git_blame__origin *parent;
long tlno;
long plno;
}blame_chunk_cb_data;
static bool same_suspect(git_blame__origin *a, git_blame__origin *b)
{
if (a == b)
return true;
if (git_oid_cmp(git_commit_id(a->commit), git_commit_id(b->commit)))
return false;
return 0 == strcmp(a->path, b->path);
}
/* find the line number of the last line the target is suspected for */
static bool find_last_in_target(size_t *out, git_blame *blame, git_blame__origin *target)
{
git_blame__entry *e;
size_t last_in_target = 0;
bool found = false;
*out = 0;
for (e=blame->ent; e; e=e->next) {
if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (last_in_target < e->s_lno + e->num_lines) {
found = true;
last_in_target = e->s_lno + e->num_lines;
}
}
*out = last_in_target;
return found;
}
/*
* It is known that lines between tlno to same came from parent, and e
* has an overlap with that range. it also is known that parent's
* line plno corresponds to e's line tlno.
*
* <---- e ----->
* <------> (entirely within)
* <------------> (extends past)
* <------------> (starts before)
* <------------------> (entirely encloses)
*
* Split e into potentially three parts; before this chunk, the chunk
* to be blamed for the parent, and after that portion.
*/
static void split_overlap(git_blame__entry *split, git_blame__entry *e,
size_t tlno, size_t plno, size_t same, git_blame__origin *parent)
{
size_t chunk_end_lno;
if (e->s_lno < tlno) {
/* there is a pre-chunk part not blamed on the parent */
split[0].suspect = origin_incref(e->suspect);
split[0].lno = e->lno;
split[0].s_lno = e->s_lno;
split[0].num_lines = tlno - e->s_lno;
split[1].lno = e->lno + tlno - e->s_lno;
split[1].s_lno = plno;
} else {
split[1].lno = e->lno;
split[1].s_lno = plno + (e->s_lno - tlno);
}
if (same < e->s_lno + e->num_lines) {
/* there is a post-chunk part not blamed on parent */
split[2].suspect = origin_incref(e->suspect);
split[2].lno = e->lno + (same - e->s_lno);
split[2].s_lno = e->s_lno + (same - e->s_lno);
split[2].num_lines = e->s_lno + e->num_lines - same;
chunk_end_lno = split[2].lno;
} else {
chunk_end_lno = e->lno + e->num_lines;
}
split[1].num_lines = chunk_end_lno - split[1].lno;
/*
* if it turns out there is nothing to blame the parent for, forget about
* the splitting. !split[1].suspect signals this.
*/
if (split[1].num_lines < 1)
return;
split[1].suspect = origin_incref(parent);
}
/*
* Link in a new blame entry to the scoreboard. Entries that cover the same
* line range have been removed from the scoreboard previously.
*/
static void add_blame_entry(git_blame *blame, git_blame__entry *e)
{
git_blame__entry *ent, *prev = NULL;
origin_incref(e->suspect);
for (ent = blame->ent; ent && ent->lno < e->lno; ent = ent->next)
prev = ent;
/* prev, if not NULL, is the last one that is below e */
e->prev = prev;
if (prev) {
e->next = prev->next;
prev->next = e;
} else {
e->next = blame->ent;
blame->ent = e;
}
if (e->next)
e->next->prev = e;
}
/*
* src typically is on-stack; we want to copy the information in it to
* a malloced blame_entry that is already on the linked list of the scoreboard.
* The origin of dst loses a refcnt while the origin of src gains one.
*/
static void dup_entry(git_blame__entry *dst, git_blame__entry *src)
{
git_blame__entry *p, *n;
p = dst->prev;
n = dst->next;
origin_incref(src->suspect);
origin_decref(dst->suspect);
memcpy(dst, src, sizeof(*src));
dst->prev = p;
dst->next = n;
dst->score = 0;
}
/*
* split_overlap() divided an existing blame e into up to three parts in split.
* Adjust the linked list of blames in the scoreboard to reflect the split.
*/
static int split_blame(git_blame *blame, git_blame__entry *split, git_blame__entry *e)
{
git_blame__entry *new_entry;
if (split[0].suspect && split[2].suspect) {
/* The first part (reuse storage for the existing entry e */
dup_entry(e, &split[0]);
/* The last part -- me */
new_entry = git__malloc(sizeof(*new_entry));
GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
/* ... and the middle part -- parent */
new_entry = git__malloc(sizeof(*new_entry));
GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else if (!split[0].suspect && !split[2].suspect) {
/*
* The parent covers the entire area; reuse storage for e and replace it
* with the parent
*/
dup_entry(e, &split[1]);
} else if (split[0].suspect) {
/* me and then parent */
dup_entry(e, &split[0]);
new_entry = git__malloc(sizeof(*new_entry));
GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[1]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
} else {
/* parent and then me */
dup_entry(e, &split[1]);
new_entry = git__malloc(sizeof(*new_entry));
GIT_ERROR_CHECK_ALLOC(new_entry);
memcpy(new_entry, &(split[2]), sizeof(git_blame__entry));
add_blame_entry(blame, new_entry);
}
return 0;
}
/*
* After splitting the blame, the origins used by the on-stack blame_entry
* should lose one refcnt each.
*/
static void decref_split(git_blame__entry *split)
{
int i;
for (i=0; i<3; i++)
origin_decref(split[i].suspect);
}
/*
* Helper for blame_chunk(). blame_entry e is known to overlap with the patch
* hunk; split it and pass blame to the parent.
*/
static int blame_overlap(
git_blame *blame,
git_blame__entry *e,
size_t tlno,
size_t plno,
size_t same,
git_blame__origin *parent)
{
git_blame__entry split[3] = {{0}};
split_overlap(split, e, tlno, plno, same, parent);
if (split[1].suspect)
if (split_blame(blame, split, e) < 0)
return -1;
decref_split(split);
return 0;
}
/*
* Process one hunk from the patch between the current suspect for blame_entry
* e and its parent. Find and split the overlap, and pass blame to the
* overlapping part to the parent.
*/
static int blame_chunk(
git_blame *blame,
size_t tlno,
size_t plno,
size_t same,
git_blame__origin *target,
git_blame__origin *parent)
{
git_blame__entry *e;
for (e = blame->ent; e; e = e->next) {
if (e->guilty || !same_suspect(e->suspect, target))
continue;
if (same <= e->s_lno)
continue;
if (tlno < e->s_lno + e->num_lines) {
if (blame_overlap(blame, e, tlno, plno, same, parent) < 0)
return -1;
}
}
return 0;
}
static int my_emit(
long start_a, long count_a,
long start_b, long count_b,
void *cb_data)
{
blame_chunk_cb_data *d = (blame_chunk_cb_data *)cb_data;
if (blame_chunk(d->blame, d->tlno, d->plno, start_b, d->target, d->parent) < 0)
return -1;
d->plno = start_a + count_a;
d->tlno = start_b + count_b;
return 0;
}
static void trim_common_tail(mmfile_t *a, mmfile_t *b, long ctx)
{
const int blk = 1024;
long trimmed = 0, recovered = 0;
char *ap = a->ptr + a->size;
char *bp = b->ptr + b->size;
long smaller = (long)((a->size < b->size) ? a->size : b->size);
if (ctx)
return;
while (blk + trimmed <= smaller && !memcmp(ap - blk, bp - blk, blk)) {
trimmed += blk;
ap -= blk;
bp -= blk;
}
while (recovered < trimmed)
if (ap[recovered++] == '\n')
break;
a->size -= trimmed - recovered;
b->size -= trimmed - recovered;
}
static int diff_hunks(mmfile_t file_a, mmfile_t file_b, void *cb_data)
{
xpparam_t xpp = {0};
xdemitconf_t xecfg = {0};
xdemitcb_t ecb = {0};
xecfg.hunk_func = my_emit;
ecb.priv = cb_data;
trim_common_tail(&file_a, &file_b, 0);
if (file_a.size > GIT_XDIFF_MAX_SIZE ||
file_b.size > GIT_XDIFF_MAX_SIZE) {
git_error_set(GIT_ERROR_INVALID, "file too large to blame");
return -1;
}
return xdl_diff(&file_a, &file_b, &xpp, &xecfg, &ecb);
}
static void fill_origin_blob(git_blame__origin *o, mmfile_t *file)
{
memset(file, 0, sizeof(*file));
if (o->blob) {
file->ptr = (char*)git_blob_rawcontent(o->blob);
file->size = (size_t)git_blob_rawsize(o->blob);
}
}
static int pass_blame_to_parent(
git_blame *blame,
git_blame__origin *target,
git_blame__origin *parent)
{
size_t last_in_target;
mmfile_t file_p, file_o;
blame_chunk_cb_data d = { blame, target, parent, 0, 0 };
if (!find_last_in_target(&last_in_target, blame, target))
return 1; /* nothing remains for this target */
fill_origin_blob(parent, &file_p);
fill_origin_blob(target, &file_o);
if (diff_hunks(file_p, file_o, &d) < 0)
return -1;
/* The reset (i.e. anything after tlno) are the same as the parent */
if (blame_chunk(blame, d.tlno, d.plno, last_in_target, target, parent) < 0)
return -1;
return 0;
}
static int paths_on_dup(void **old, void *new)
{
GIT_UNUSED(old);
git__free(new);
return -1;
}
static git_blame__origin* find_origin(
git_blame *blame,
git_commit *parent,
git_blame__origin *origin)
{
git_blame__origin *porigin = NULL;
git_diff *difflist = NULL;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_tree *otree=NULL, *ptree=NULL;
/* Get the trees from this commit and its parent */
if (0 != git_commit_tree(&otree, origin->commit) ||
0 != git_commit_tree(&ptree, parent))
goto cleanup;
/* Configure the diff */
diffopts.context_lines = 0;
diffopts.flags = GIT_DIFF_SKIP_BINARY_CHECK;
/* Check to see if files we're interested have changed */
diffopts.pathspec.count = blame->paths.length;
diffopts.pathspec.strings = (char**)blame->paths.contents;
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
goto cleanup;
if (!git_diff_num_deltas(difflist)) {
/* No changes; copy data */
git_blame__get_origin(&porigin, blame, parent, origin->path);
} else {
git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
int i;
/* Generate a full diff between the two trees */
git_diff_free(difflist);
diffopts.pathspec.count = 0;
if (0 != git_diff_tree_to_tree(&difflist, blame->repository, ptree, otree, &diffopts))
goto cleanup;
/* Let diff find renames */
findopts.flags = GIT_DIFF_FIND_RENAMES;
if (0 != git_diff_find_similar(difflist, &findopts))
goto cleanup;
/* Find one that matches */
for (i=0; i<(int)git_diff_num_deltas(difflist); i++) {
const git_diff_delta *delta = git_diff_get_delta(difflist, i);
if (!git_vector_bsearch(NULL, &blame->paths, delta->new_file.path))
{
git_vector_insert_sorted(&blame->paths, (void*)git__strdup(delta->old_file.path),
paths_on_dup);
make_origin(&porigin, parent, delta->old_file.path);
}
}
}
cleanup:
git_diff_free(difflist);
git_tree_free(otree);
git_tree_free(ptree);
return porigin;
}
/*
* The blobs of origin and porigin exactly match, so everything origin is
* suspected for can be blamed on the parent.
*/
static int pass_whole_blame(git_blame *blame,
git_blame__origin *origin, git_blame__origin *porigin)
{
git_blame__entry *e;
if (!porigin->blob &&
git_object_lookup((git_object**)&porigin->blob, blame->repository,
git_blob_id(origin->blob), GIT_OBJECT_BLOB) < 0)
return -1;
for (e=blame->ent; e; e=e->next) {
if (!same_suspect(e->suspect, origin))
continue;
origin_incref(porigin);
origin_decref(e->suspect);
e->suspect = porigin;
}
return 0;
}
static int pass_blame(git_blame *blame, git_blame__origin *origin, uint32_t opt)
{
git_commit *commit = origin->commit;
int i, num_parents;
git_blame__origin *sg_buf[16];
git_blame__origin *porigin, **sg_origin = sg_buf;
int ret, error = 0;
num_parents = git_commit_parentcount(commit);
if (!git_oid_cmp(git_commit_id(commit), &blame->options.oldest_commit))
/* Stop at oldest specified commit */
num_parents = 0;
else if (opt & GIT_BLAME_FIRST_PARENT && num_parents > 1)
/* Limit search to the first parent */
num_parents = 1;
if (!num_parents) {
git_oid_cpy(&blame->options.oldest_commit, git_commit_id(commit));
goto finish;
} else if (num_parents < (int)ARRAY_SIZE(sg_buf))
memset(sg_buf, 0, sizeof(sg_buf));
else {
sg_origin = git__calloc(num_parents, sizeof(*sg_origin));
GIT_ERROR_CHECK_ALLOC(sg_origin);
}
for (i=0; i<num_parents; i++) {
git_commit *p;
int j, same;
if (sg_origin[i])
continue;
if ((error = git_commit_parent(&p, origin->commit, i)) < 0)
goto finish;
porigin = find_origin(blame, p, origin);
if (!porigin) {
/*
* We only have to decrement the parent's
* reference count when no porigin has
* been created, as otherwise the commit
* is assigned to the created object.
*/
git_commit_free(p);
continue;
}
if (porigin->blob && origin->blob &&
!git_oid_cmp(git_blob_id(porigin->blob), git_blob_id(origin->blob))) {
error = pass_whole_blame(blame, origin, porigin);
origin_decref(porigin);
goto finish;
}
for (j = same = 0; j<i; j++)
if (sg_origin[j] &&
!git_oid_cmp(git_blob_id(sg_origin[j]->blob), git_blob_id(porigin->blob))) {
same = 1;
break;
}
if (!same)
sg_origin[i] = porigin;
else
origin_decref(porigin);
}
/* Standard blame */
for (i=0; i<num_parents; i++) {
git_blame__origin *porigin = sg_origin[i];
if (!porigin)
continue;
if (!origin->previous) {
origin_incref(porigin);
origin->previous = porigin;
}
if ((ret = pass_blame_to_parent(blame, origin, porigin)) != 0) {
if (ret < 0)
error = -1;
goto finish;
}
}
/* TODO: optionally find moves in parents' files */
/* TODO: optionally find copies in parents' files */
finish:
for (i=0; i<num_parents; i++)
if (sg_origin[i])
origin_decref(sg_origin[i]);
if (sg_origin != sg_buf)
git__free(sg_origin);
return error;
}
/*
* If two blame entries that are next to each other came from
* contiguous lines in the same origin (i.e. <commit, path> pair),
* merge them together.
*/
static void coalesce(git_blame *blame)
{
git_blame__entry *ent, *next;
for (ent=blame->ent; ent && (next = ent->next); ent = next) {
if (same_suspect(ent->suspect, next->suspect) &&
ent->guilty == next->guilty &&
ent->s_lno + ent->num_lines == next->s_lno)
{
ent->num_lines += next->num_lines;
ent->next = next->next;
if (ent->next)
ent->next->prev = ent;
origin_decref(next->suspect);
git__free(next);
ent->score = 0;
next = ent; /* again */
}
}
}
int git_blame__like_git(git_blame *blame, uint32_t opt)
{
int error = 0;
while (true) {
git_blame__entry *ent;
git_blame__origin *suspect = NULL;
/* Find a suspect to break down */
for (ent = blame->ent; !suspect && ent; ent = ent->next)
if (!ent->guilty)
suspect = ent->suspect;
if (!suspect)
break;
/* We'll use this suspect later in the loop, so hold on to it for now. */
origin_incref(suspect);
if ((error = pass_blame(blame, suspect, opt)) < 0)
break;
/* Take responsibility for the remaining entries */
for (ent = blame->ent; ent; ent = ent->next) {
if (same_suspect(ent->suspect, suspect)) {
ent->guilty = true;
ent->is_boundary = !git_oid_cmp(
git_commit_id(suspect->commit),
&blame->options.oldest_commit);
}
}
origin_decref(suspect);
}
if (!error)
coalesce(blame);
return error;
}
void git_blame__free_entry(git_blame__entry *ent)
{
if (!ent) return;
origin_decref(ent->suspect);
git__free(ent);
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_blame_git__
#define INCLUDE_blame_git__
#include "common.h"
#include "blame.h"
int git_blame__get_origin(
git_blame__origin **out,
git_blame *sb,
git_commit *commit,
const char *path);
void git_blame__free_entry(git_blame__entry *ent);
int git_blame__like_git(git_blame *sb, uint32_t flags);
#endif
+493
View File
@@ -0,0 +1,493 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "blob.h"
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/odb_backend.h"
#include "filebuf.h"
#include "filter.h"
#include "buf_text.h"
const void *git_blob_rawcontent(const git_blob *blob)
{
assert(blob);
if (blob->raw)
return blob->data.raw.data;
else
return git_odb_object_data(blob->data.odb);
}
git_object_size_t git_blob_rawsize(const git_blob *blob)
{
assert(blob);
if (blob->raw)
return blob->data.raw.size;
else
return (git_object_size_t)git_odb_object_size(blob->data.odb);
}
int git_blob__getbuf(git_buf *buffer, git_blob *blob)
{
git_object_size_t size = git_blob_rawsize(blob);
GIT_ERROR_CHECK_BLOBSIZE(size);
return git_buf_set(buffer, git_blob_rawcontent(blob), (size_t)size);
}
void git_blob__free(void *_blob)
{
git_blob *blob = (git_blob *) _blob;
if (!blob->raw)
git_odb_object_free(blob->data.odb);
git__free(blob);
}
int git_blob__parse_raw(void *_blob, const char *data, size_t size)
{
git_blob *blob = (git_blob *) _blob;
assert(blob);
blob->raw = 1;
blob->data.raw.data = data;
blob->data.raw.size = size;
return 0;
}
int git_blob__parse(void *_blob, git_odb_object *odb_obj)
{
git_blob *blob = (git_blob *) _blob;
assert(blob);
git_cached_obj_incref((git_cached_obj *)odb_obj);
blob->raw = 0;
blob->data.odb = odb_obj;
return 0;
}
int git_blob_create_from_buffer(
git_oid *id, git_repository *repo, const void *buffer, size_t len)
{
int error;
git_odb *odb;
git_odb_stream *stream;
assert(id && repo);
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
(error = git_odb_open_wstream(&stream, odb, len, GIT_OBJECT_BLOB)) < 0)
return error;
if ((error = git_odb_stream_write(stream, buffer, len)) == 0)
error = git_odb_stream_finalize_write(id, stream);
git_odb_stream_free(stream);
return error;
}
static int write_file_stream(
git_oid *id, git_odb *odb, const char *path, git_object_size_t file_size)
{
int fd, error;
char buffer[FILEIO_BUFSIZE];
git_odb_stream *stream = NULL;
ssize_t read_len = -1;
git_object_size_t written = 0;
if ((error = git_odb_open_wstream(
&stream, odb, file_size, GIT_OBJECT_BLOB)) < 0)
return error;
if ((fd = git_futils_open_ro(path)) < 0) {
git_odb_stream_free(stream);
return -1;
}
while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) {
error = git_odb_stream_write(stream, buffer, read_len);
written += read_len;
}
p_close(fd);
if (written != file_size || read_len < 0) {
git_error_set(GIT_ERROR_OS, "failed to read file into stream");
error = -1;
}
if (!error)
error = git_odb_stream_finalize_write(id, stream);
git_odb_stream_free(stream);
return error;
}
static int write_file_filtered(
git_oid *id,
git_object_size_t *size,
git_odb *odb,
const char *full_path,
git_filter_list *fl)
{
int error;
git_buf tgt = GIT_BUF_INIT;
error = git_filter_list_apply_to_file(&tgt, fl, NULL, full_path);
/* Write the file to disk if it was properly filtered */
if (!error) {
*size = tgt.size;
error = git_odb_write(id, odb, tgt.ptr, tgt.size, GIT_OBJECT_BLOB);
}
git_buf_dispose(&tgt);
return error;
}
static int write_symlink(
git_oid *id, 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);
GIT_ERROR_CHECK_ALLOC(link_data);
read_len = p_readlink(path, link_data, link_size);
if (read_len != (ssize_t)link_size) {
git_error_set(GIT_ERROR_OS, "failed to create blob: cannot read symlink '%s'", path);
git__free(link_data);
return -1;
}
error = git_odb_write(id, odb, (void *)link_data, link_size, GIT_OBJECT_BLOB);
git__free(link_data);
return error;
}
int git_blob__create_from_paths(
git_oid *id,
struct stat *out_st,
git_repository *repo,
const char *content_path,
const char *hint_path,
mode_t hint_mode,
bool try_load_filters)
{
int error;
struct stat st;
git_odb *odb = NULL;
git_object_size_t size;
mode_t mode;
git_buf path = GIT_BUF_INIT;
assert(hint_path || !try_load_filters);
if (!content_path) {
if (git_repository__ensure_not_bare(repo, "create blob from file") < 0)
return GIT_EBAREREPO;
if (git_buf_joinpath(
&path, git_repository_workdir(repo), hint_path) < 0)
return -1;
content_path = path.ptr;
}
if ((error = git_path_lstat(content_path, &st)) < 0 ||
(error = git_repository_odb(&odb, repo)) < 0)
goto done;
if (S_ISDIR(st.st_mode)) {
git_error_set(GIT_ERROR_ODB, "cannot create blob from '%s': it is a directory", content_path);
error = GIT_EDIRECTORY;
goto done;
}
if (out_st)
memcpy(out_st, &st, sizeof(st));
size = st.st_size;
mode = hint_mode ? hint_mode : st.st_mode;
if (S_ISLNK(mode)) {
error = write_symlink(id, odb, content_path, (size_t)size);
} else {
git_filter_list *fl = NULL;
if (try_load_filters)
/* Load the filters for writing this file to the ODB */
error = git_filter_list_load(
&fl, repo, NULL, hint_path,
GIT_FILTER_TO_ODB, GIT_FILTER_DEFAULT);
if (error < 0)
/* well, that didn't work */;
else if (fl == NULL)
/* No filters need to be applied to the document: we can stream
* directly from disk */
error = write_file_stream(id, odb, content_path, size);
else {
/* We need to apply one or more filters */
error = write_file_filtered(id, &size, odb, content_path, fl);
git_filter_list_free(fl);
}
/*
* 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
*/
}
done:
git_odb_free(odb);
git_buf_dispose(&path);
return error;
}
int git_blob_create_from_workdir(
git_oid *id, git_repository *repo, const char *path)
{
return git_blob__create_from_paths(id, NULL, repo, NULL, path, 0, true);
}
int git_blob_create_from_disk(
git_oid *id, git_repository *repo, const char *path)
{
int error;
git_buf full_path = GIT_BUF_INIT;
const char *workdir, *hintpath;
if ((error = git_path_prettify(&full_path, path, NULL)) < 0) {
git_buf_dispose(&full_path);
return error;
}
hintpath = git_buf_cstr(&full_path);
workdir = git_repository_workdir(repo);
if (workdir && !git__prefixcmp(hintpath, workdir))
hintpath += strlen(workdir);
error = git_blob__create_from_paths(
id, NULL, repo, git_buf_cstr(&full_path), hintpath, 0, true);
git_buf_dispose(&full_path);
return error;
}
typedef struct {
git_writestream parent;
git_filebuf fbuf;
git_repository *repo;
char *hintpath;
} blob_writestream;
static int blob_writestream_close(git_writestream *_stream)
{
blob_writestream *stream = (blob_writestream *) _stream;
git_filebuf_cleanup(&stream->fbuf);
return 0;
}
static void blob_writestream_free(git_writestream *_stream)
{
blob_writestream *stream = (blob_writestream *) _stream;
git_filebuf_cleanup(&stream->fbuf);
git__free(stream->hintpath);
git__free(stream);
}
static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len)
{
blob_writestream *stream = (blob_writestream *) _stream;
return git_filebuf_write(&stream->fbuf, buffer, len);
}
int git_blob_create_from_stream(git_writestream **out, git_repository *repo, const char *hintpath)
{
int error;
git_buf path = GIT_BUF_INIT;
blob_writestream *stream;
assert(out && repo);
stream = git__calloc(1, sizeof(blob_writestream));
GIT_ERROR_CHECK_ALLOC(stream);
if (hintpath) {
stream->hintpath = git__strdup(hintpath);
GIT_ERROR_CHECK_ALLOC(stream->hintpath);
}
stream->repo = repo;
stream->parent.write = blob_writestream_write;
stream->parent.close = blob_writestream_close;
stream->parent.free = blob_writestream_free;
if ((error = git_repository_item_path(&path, repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
|| (error = git_buf_joinpath(&path, path.ptr, "streamed")) < 0)
goto cleanup;
if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY,
0666, 2 * 1024 * 1024)) < 0)
goto cleanup;
*out = (git_writestream *) stream;
cleanup:
if (error < 0)
blob_writestream_free((git_writestream *) stream);
git_buf_dispose(&path);
return error;
}
int git_blob_create_from_stream_commit(git_oid *out, git_writestream *_stream)
{
int error;
blob_writestream *stream = (blob_writestream *) _stream;
/*
* We can make this more officient by avoiding writing to
* disk, but for now let's re-use the helper functions we
* have.
*/
if ((error = git_filebuf_flush(&stream->fbuf)) < 0)
goto cleanup;
error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock,
stream->hintpath, 0, !!stream->hintpath);
cleanup:
blob_writestream_free(_stream);
return error;
}
int git_blob_is_binary(const git_blob *blob)
{
git_buf content = GIT_BUF_INIT;
git_object_size_t size;
assert(blob);
size = git_blob_rawsize(blob);
git_buf_attach_notowned(&content, git_blob_rawcontent(blob),
(size_t)min(size, GIT_FILTER_BYTES_TO_CHECK_NUL));
return git_buf_text_is_binary(&content);
}
int git_blob_filter(
git_buf *out,
git_blob *blob,
const char *path,
git_blob_filter_options *given_opts)
{
int error = 0;
git_filter_list *fl = NULL;
git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
git_filter_flag_t flags = GIT_FILTER_DEFAULT;
assert(blob && path && out);
git_buf_sanitize(out);
GIT_ERROR_CHECK_VERSION(
given_opts, GIT_BLOB_FILTER_OPTIONS_VERSION, "git_blob_filter_options");
if (given_opts != NULL)
memcpy(&opts, given_opts, sizeof(git_blob_filter_options));
if ((opts.flags & GIT_BLOB_FILTER_CHECK_FOR_BINARY) != 0 &&
git_blob_is_binary(blob))
return 0;
if ((opts.flags & GIT_BLOB_FILTER_NO_SYSTEM_ATTRIBUTES) != 0)
flags |= GIT_FILTER_NO_SYSTEM_ATTRIBUTES;
if ((opts.flags & GIT_BLOB_FILTER_ATTTRIBUTES_FROM_HEAD) != 0)
flags |= GIT_FILTER_ATTRIBUTES_FROM_HEAD;
if (!(error = git_filter_list_load(
&fl, git_blob_owner(blob), blob, path,
GIT_FILTER_TO_WORKTREE, flags))) {
error = git_filter_list_apply_to_blob(out, fl, blob);
git_filter_list_free(fl);
}
return error;
}
/* Deprecated functions */
int git_blob_create_frombuffer(
git_oid *id, git_repository *repo, const void *buffer, size_t len)
{
return git_blob_create_from_buffer(id, repo, buffer, len);
}
int git_blob_create_fromworkdir(git_oid *id, git_repository *repo, const char *relative_path)
{
return git_blob_create_from_workdir(id, repo, relative_path);
}
int git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path)
{
return git_blob_create_from_disk(id, repo, path);
}
int git_blob_create_fromstream(
git_writestream **out,
git_repository *repo,
const char *hintpath)
{
return git_blob_create_from_stream(out, repo, hintpath);
}
int git_blob_create_fromstream_commit(
git_oid *out,
git_writestream *stream)
{
return git_blob_create_from_stream_commit(out, stream);
}
int git_blob_filtered_content(
git_buf *out,
git_blob *blob,
const char *path,
int check_for_binary_data)
{
git_blob_filter_options opts = GIT_BLOB_FILTER_OPTIONS_INIT;
if (check_for_binary_data)
opts.flags |= GIT_BLOB_FILTER_CHECK_FOR_BINARY;
else
opts.flags &= ~GIT_BLOB_FILTER_CHECK_FOR_BINARY;
return git_blob_filter(out, blob, path, &opts);
}
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_blob_h__
#define INCLUDE_blob_h__
#include "common.h"
#include "git2/blob.h"
#include "repository.h"
#include "odb.h"
#include "futils.h"
struct git_blob {
git_object object;
union {
git_odb_object *odb;
struct {
const char *data;
git_object_size_t size;
} raw;
} data;
unsigned int raw:1;
};
#define GIT_ERROR_CHECK_BLOBSIZE(n) \
do { \
if (!git__is_sizet(n)) { \
git_error_set(GIT_ERROR_NOMEMORY, "blob contents too large to fit in memory"); \
return -1; \
} \
} while(0)
void git_blob__free(void *blob);
int git_blob__parse(void *blob, git_odb_object *obj);
int git_blob__parse_raw(void *blob, const char *data, size_t size);
int git_blob__getbuf(git_buf *buffer, git_blob *blob);
extern int git_blob__create_from_paths(
git_oid *out_oid,
struct stat *out_st,
git_repository *repo,
const char *full_path,
const char *hint_path,
mode_t hint_mode,
bool apply_filters);
#endif
+727
View File
@@ -0,0 +1,727 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "branch.h"
#include "commit.h"
#include "tag.h"
#include "config.h"
#include "refspec.h"
#include "refs.h"
#include "remote.h"
#include "annotated_commit.h"
#include "worktree.h"
#include "git2/branch.h"
static int retrieve_branch_reference(
git_reference **branch_reference_out,
git_repository *repo,
const char *branch_name,
bool is_remote)
{
git_reference *branch = NULL;
int error = 0;
char *prefix;
git_buf ref_name = GIT_BUF_INIT;
prefix = is_remote ? GIT_REFS_REMOTES_DIR : GIT_REFS_HEADS_DIR;
if ((error = git_buf_joinpath(&ref_name, prefix, branch_name)) < 0)
/* OOM */;
else if ((error = git_reference_lookup(&branch, repo, ref_name.ptr)) < 0)
git_error_set(
GIT_ERROR_REFERENCE, "cannot locate %s branch '%s'",
is_remote ? "remote-tracking" : "local", branch_name);
*branch_reference_out = branch; /* will be NULL on error */
git_buf_dispose(&ref_name);
return error;
}
static int not_a_local_branch(const char *reference_name)
{
git_error_set(
GIT_ERROR_INVALID,
"reference '%s' is not a local branch.", reference_name);
return -1;
}
static int create_branch(
git_reference **ref_out,
git_repository *repository,
const char *branch_name,
const git_commit *commit,
const char *from,
int force)
{
int is_unmovable_head = 0;
git_reference *branch = NULL;
git_buf canonical_branch_name = GIT_BUF_INIT,
log_message = GIT_BUF_INIT;
int error = -1;
int bare = git_repository_is_bare(repository);
assert(branch_name && commit && ref_out);
assert(git_object_owner((const git_object *)commit) == repository);
if (!git__strcmp(branch_name, "HEAD")) {
git_error_set(GIT_ERROR_REFERENCE, "'HEAD' is not a valid branch name");
error = -1;
goto cleanup;
}
if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) {
error = git_branch_is_head(branch);
git_reference_free(branch);
branch = NULL;
if (error < 0)
goto cleanup;
is_unmovable_head = error;
}
if (is_unmovable_head && force) {
git_error_set(GIT_ERROR_REFERENCE, "cannot force update branch '%s' as it is "
"the current HEAD of the repository.", branch_name);
error = -1;
goto cleanup;
}
if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0)
goto cleanup;
if (git_buf_printf(&log_message, "branch: Created from %s", from) < 0)
goto cleanup;
error = git_reference_create(&branch, repository,
git_buf_cstr(&canonical_branch_name), git_commit_id(commit), force,
git_buf_cstr(&log_message));
if (!error)
*ref_out = branch;
cleanup:
git_buf_dispose(&canonical_branch_name);
git_buf_dispose(&log_message);
return error;
}
int git_branch_create(
git_reference **ref_out,
git_repository *repository,
const char *branch_name,
const git_commit *commit,
int force)
{
return create_branch(ref_out, repository, branch_name, commit, git_oid_tostr_s(git_commit_id(commit)), force);
}
int git_branch_create_from_annotated(
git_reference **ref_out,
git_repository *repository,
const char *branch_name,
const git_annotated_commit *commit,
int force)
{
return create_branch(ref_out,
repository, branch_name, commit->commit, commit->description, force);
}
static int branch_equals(git_repository *repo, const char *path, void *payload)
{
git_reference *branch = (git_reference *) payload;
git_reference *head = NULL;
int equal = 0;
if (git_reference__read_head(&head, repo, path) < 0 ||
git_reference_type(head) != GIT_REFERENCE_SYMBOLIC)
goto done;
equal = !git__strcmp(head->target.symbolic, branch->name);
done:
git_reference_free(head);
return equal;
}
int git_branch_is_checked_out(const git_reference *branch)
{
git_repository *repo;
int flags = 0;
assert(branch);
if (!git_reference_is_branch(branch))
return 0;
repo = git_reference_owner(branch);
if (git_repository_is_bare(repo))
flags |= GIT_REPOSITORY_FOREACH_HEAD_SKIP_REPO;
return git_repository_foreach_head(repo, branch_equals, flags, (void *) branch) == 1;
}
int git_branch_delete(git_reference *branch)
{
int is_head;
git_buf config_section = GIT_BUF_INIT;
int error = -1;
assert(branch);
if (!git_reference_is_branch(branch) && !git_reference_is_remote(branch)) {
git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a valid branch.",
git_reference_name(branch));
return GIT_ENOTFOUND;
}
if ((is_head = git_branch_is_head(branch)) < 0)
return is_head;
if (is_head) {
git_error_set(GIT_ERROR_REFERENCE, "cannot delete branch '%s' as it is "
"the current HEAD of the repository.", git_reference_name(branch));
return -1;
}
if (git_reference_is_branch(branch) && git_branch_is_checked_out(branch)) {
git_error_set(GIT_ERROR_REFERENCE, "Cannot delete branch '%s' as it is "
"the current HEAD of a linked repository.", git_reference_name(branch));
return -1;
}
if (git_buf_join(&config_section, '.', "branch",
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0)
goto on_error;
if (git_config_rename_section(
git_reference_owner(branch), git_buf_cstr(&config_section), NULL) < 0)
goto on_error;
error = git_reference_delete(branch);
on_error:
git_buf_dispose(&config_section);
return error;
}
typedef struct {
git_reference_iterator *iter;
unsigned int flags;
} branch_iter;
int git_branch_next(git_reference **out, git_branch_t *out_type, git_branch_iterator *_iter)
{
branch_iter *iter = (branch_iter *) _iter;
git_reference *ref;
int error;
while ((error = git_reference_next(&ref, iter->iter)) == 0) {
if ((iter->flags & GIT_BRANCH_LOCAL) &&
!git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR)) {
*out = ref;
*out_type = GIT_BRANCH_LOCAL;
return 0;
} else if ((iter->flags & GIT_BRANCH_REMOTE) &&
!git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR)) {
*out = ref;
*out_type = GIT_BRANCH_REMOTE;
return 0;
} else {
git_reference_free(ref);
}
}
return error;
}
int git_branch_iterator_new(
git_branch_iterator **out,
git_repository *repo,
git_branch_t list_flags)
{
branch_iter *iter;
iter = git__calloc(1, sizeof(branch_iter));
GIT_ERROR_CHECK_ALLOC(iter);
iter->flags = list_flags;
if (git_reference_iterator_new(&iter->iter, repo) < 0) {
git__free(iter);
return -1;
}
*out = (git_branch_iterator *) iter;
return 0;
}
void git_branch_iterator_free(git_branch_iterator *_iter)
{
branch_iter *iter = (branch_iter *) _iter;
if (iter == NULL)
return;
git_reference_iterator_free(iter->iter);
git__free(iter);
}
int git_branch_move(
git_reference **out,
git_reference *branch,
const char *new_branch_name,
int force)
{
git_buf new_reference_name = GIT_BUF_INIT,
old_config_section = GIT_BUF_INIT,
new_config_section = GIT_BUF_INIT,
log_message = GIT_BUF_INIT;
int error;
assert(branch && new_branch_name);
if (!git_reference_is_branch(branch))
return not_a_local_branch(git_reference_name(branch));
if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0)
goto done;
if ((error = git_buf_printf(&log_message, "branch: renamed %s to %s",
git_reference_name(branch), git_buf_cstr(&new_reference_name))) < 0)
goto done;
/* first update ref then config so failure won't trash config */
error = git_reference_rename(
out, branch, git_buf_cstr(&new_reference_name), force,
git_buf_cstr(&log_message));
if (error < 0)
goto done;
git_buf_join(&old_config_section, '.', "branch",
git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR));
git_buf_join(&new_config_section, '.', "branch", new_branch_name);
error = git_config_rename_section(
git_reference_owner(branch),
git_buf_cstr(&old_config_section),
git_buf_cstr(&new_config_section));
done:
git_buf_dispose(&new_reference_name);
git_buf_dispose(&old_config_section);
git_buf_dispose(&new_config_section);
git_buf_dispose(&log_message);
return error;
}
int git_branch_lookup(
git_reference **ref_out,
git_repository *repo,
const char *branch_name,
git_branch_t branch_type)
{
int error = -1;
assert(ref_out && repo && branch_name);
switch (branch_type) {
case GIT_BRANCH_LOCAL:
case GIT_BRANCH_REMOTE:
error = retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE);
break;
case GIT_BRANCH_ALL:
error = retrieve_branch_reference(ref_out, repo, branch_name, false);
if (error == GIT_ENOTFOUND)
error = retrieve_branch_reference(ref_out, repo, branch_name, true);
break;
default:
assert(false);
}
return error;
}
int git_branch_name(
const char **out,
const git_reference *ref)
{
const char *branch_name;
assert(out && ref);
branch_name = ref->name;
if (git_reference_is_branch(ref)) {
branch_name += strlen(GIT_REFS_HEADS_DIR);
} else if (git_reference_is_remote(ref)) {
branch_name += strlen(GIT_REFS_REMOTES_DIR);
} else {
git_error_set(GIT_ERROR_INVALID,
"reference '%s' is neither a local nor a remote branch.", ref->name);
return -1;
}
*out = branch_name;
return 0;
}
static int retrieve_upstream_configuration(
git_buf *out,
const git_config *config,
const char *canonical_branch_name,
const char *format)
{
git_buf buf = GIT_BUF_INIT;
int error;
if (git_buf_printf(&buf, format,
canonical_branch_name + strlen(GIT_REFS_HEADS_DIR)) < 0)
return -1;
error = git_config_get_string_buf(out, config, git_buf_cstr(&buf));
git_buf_dispose(&buf);
return error;
}
int git_branch_upstream_name(
git_buf *out,
git_repository *repo,
const char *refname)
{
git_buf remote_name = GIT_BUF_INIT;
git_buf merge_name = GIT_BUF_INIT;
git_buf buf = GIT_BUF_INIT;
int error = -1;
git_remote *remote = NULL;
const git_refspec *refspec;
git_config *config;
assert(out && refname);
git_buf_sanitize(out);
if (!git_reference__is_branch(refname))
return not_a_local_branch(refname);
if ((error = git_repository_config_snapshot(&config, repo)) < 0)
return error;
if ((error = retrieve_upstream_configuration(
&remote_name, config, refname, "branch.%s.remote")) < 0)
goto cleanup;
if ((error = retrieve_upstream_configuration(
&merge_name, config, refname, "branch.%s.merge")) < 0)
goto cleanup;
if (git_buf_len(&remote_name) == 0 || git_buf_len(&merge_name) == 0) {
git_error_set(GIT_ERROR_REFERENCE,
"branch '%s' does not have an upstream", refname);
error = GIT_ENOTFOUND;
goto cleanup;
}
if (strcmp(".", git_buf_cstr(&remote_name)) != 0) {
if ((error = git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name))) < 0)
goto cleanup;
refspec = git_remote__matching_refspec(remote, git_buf_cstr(&merge_name));
if (!refspec) {
error = GIT_ENOTFOUND;
goto cleanup;
}
if (git_refspec_transform(&buf, refspec, git_buf_cstr(&merge_name)) < 0)
goto cleanup;
} else
if (git_buf_set(&buf, git_buf_cstr(&merge_name), git_buf_len(&merge_name)) < 0)
goto cleanup;
error = git_buf_set(out, git_buf_cstr(&buf), git_buf_len(&buf));
cleanup:
git_config_free(config);
git_remote_free(remote);
git_buf_dispose(&remote_name);
git_buf_dispose(&merge_name);
git_buf_dispose(&buf);
return error;
}
int git_branch_upstream_remote(git_buf *buf, git_repository *repo, const char *refname)
{
int error;
git_config *cfg;
if (!git_reference__is_branch(refname))
return not_a_local_branch(refname);
if ((error = git_repository_config__weakptr(&cfg, repo)) < 0)
return error;
git_buf_sanitize(buf);
if ((error = retrieve_upstream_configuration(buf, cfg, refname, "branch.%s.remote")) < 0)
return error;
if (git_buf_len(buf) == 0) {
git_error_set(GIT_ERROR_REFERENCE, "branch '%s' does not have an upstream remote", refname);
error = GIT_ENOTFOUND;
git_buf_clear(buf);
}
return error;
}
int git_branch_remote_name(git_buf *buf, git_repository *repo, const char *refname)
{
git_strarray remote_list = {0};
size_t i;
git_remote *remote;
const git_refspec *fetchspec;
int error = 0;
char *remote_name = NULL;
assert(buf && repo && refname);
git_buf_sanitize(buf);
/* Verify that this is a remote branch */
if (!git_reference__is_remote(refname)) {
git_error_set(GIT_ERROR_INVALID, "reference '%s' is not a remote branch.",
refname);
error = GIT_ERROR;
goto cleanup;
}
/* Get the remotes */
if ((error = git_remote_list(&remote_list, repo)) < 0)
goto cleanup;
/* Find matching remotes */
for (i = 0; i < remote_list.count; i++) {
if ((error = git_remote_lookup(&remote, repo, remote_list.strings[i])) < 0)
continue;
fetchspec = git_remote__matching_dst_refspec(remote, refname);
if (fetchspec) {
/* If we have not already set out yet, then set
* it to the matching remote name. Otherwise
* multiple remotes match this reference, and it
* is ambiguous. */
if (!remote_name) {
remote_name = remote_list.strings[i];
} else {
git_remote_free(remote);
git_error_set(GIT_ERROR_REFERENCE,
"reference '%s' is ambiguous", refname);
error = GIT_EAMBIGUOUS;
goto cleanup;
}
}
git_remote_free(remote);
}
if (remote_name) {
git_buf_clear(buf);
error = git_buf_puts(buf, remote_name);
} else {
git_error_set(GIT_ERROR_REFERENCE,
"could not determine remote for '%s'", refname);
error = GIT_ENOTFOUND;
}
cleanup:
if (error < 0)
git_buf_dispose(buf);
git_strarray_free(&remote_list);
return error;
}
int git_branch_upstream(
git_reference **tracking_out,
const git_reference *branch)
{
int error;
git_buf tracking_name = GIT_BUF_INIT;
if ((error = git_branch_upstream_name(&tracking_name,
git_reference_owner(branch), git_reference_name(branch))) < 0)
return error;
error = git_reference_lookup(
tracking_out,
git_reference_owner(branch),
git_buf_cstr(&tracking_name));
git_buf_dispose(&tracking_name);
return error;
}
static int unset_upstream(git_config *config, const char *shortname)
{
git_buf buf = GIT_BUF_INIT;
if (git_buf_printf(&buf, "branch.%s.remote", shortname) < 0)
return -1;
if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
git_buf_clear(&buf);
if (git_buf_printf(&buf, "branch.%s.merge", shortname) < 0)
goto on_error;
if (git_config_delete_entry(config, git_buf_cstr(&buf)) < 0)
goto on_error;
git_buf_dispose(&buf);
return 0;
on_error:
git_buf_dispose(&buf);
return -1;
}
int git_branch_set_upstream(git_reference *branch, const char *branch_name)
{
git_buf key = GIT_BUF_INIT, remote_name = GIT_BUF_INIT, merge_refspec = GIT_BUF_INIT;
git_reference *upstream;
git_repository *repo;
git_remote *remote = NULL;
git_config *config;
const char *refname, *shortname;
int local, error;
const git_refspec *fetchspec;
refname = git_reference_name(branch);
if (!git_reference__is_branch(refname))
return not_a_local_branch(refname);
if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0)
return -1;
shortname = refname + strlen(GIT_REFS_HEADS_DIR);
/* We're unsetting, delegate and bail-out */
if (branch_name == NULL)
return unset_upstream(config, shortname);
repo = git_reference_owner(branch);
/* First we need to resolve name to a branch */
if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_LOCAL) == 0)
local = 1;
else if (git_branch_lookup(&upstream, repo, branch_name, GIT_BRANCH_REMOTE) == 0)
local = 0;
else {
git_error_set(GIT_ERROR_REFERENCE,
"cannot set upstream for branch '%s'", shortname);
return GIT_ENOTFOUND;
}
/*
* If it's a local-tracking branch, its remote is "." (as "the local
* repository"), and the branch name is simply the refname.
* Otherwise we need to figure out what the remote-tracking branch's
* name on the remote is and use that.
*/
if (local)
error = git_buf_puts(&remote_name, ".");
else
error = git_branch_remote_name(&remote_name, repo, git_reference_name(upstream));
if (error < 0)
goto on_error;
/* Update the upsteam branch config with the new name */
if (git_buf_printf(&key, "branch.%s.remote", shortname) < 0)
goto on_error;
if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&remote_name)) < 0)
goto on_error;
if (local) {
/* A local branch uses the upstream refname directly */
if (git_buf_puts(&merge_refspec, git_reference_name(upstream)) < 0)
goto on_error;
} else {
/* We transform the upstream branch name according to the remote's refspecs */
if (git_remote_lookup(&remote, repo, git_buf_cstr(&remote_name)) < 0)
goto on_error;
fetchspec = git_remote__matching_dst_refspec(remote, git_reference_name(upstream));
if (!fetchspec || git_refspec_rtransform(&merge_refspec, fetchspec, git_reference_name(upstream)) < 0)
goto on_error;
git_remote_free(remote);
remote = NULL;
}
/* Update the merge branch config with the refspec */
git_buf_clear(&key);
if (git_buf_printf(&key, "branch.%s.merge", shortname) < 0)
goto on_error;
if (git_config_set_string(config, git_buf_cstr(&key), git_buf_cstr(&merge_refspec)) < 0)
goto on_error;
git_reference_free(upstream);
git_buf_dispose(&key);
git_buf_dispose(&remote_name);
git_buf_dispose(&merge_refspec);
return 0;
on_error:
git_reference_free(upstream);
git_buf_dispose(&key);
git_buf_dispose(&remote_name);
git_buf_dispose(&merge_refspec);
git_remote_free(remote);
return -1;
}
int git_branch_is_head(
const git_reference *branch)
{
git_reference *head;
bool is_same = false;
int error;
assert(branch);
if (!git_reference_is_branch(branch))
return false;
error = git_repository_head(&head, git_reference_owner(branch));
if (error == GIT_EUNBORNBRANCH || error == GIT_ENOTFOUND)
return false;
if (error < 0)
return -1;
is_same = strcmp(
git_reference_name(branch),
git_reference_name(head)) == 0;
git_reference_free(head);
return is_same;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_branch_h__
#define INCLUDE_branch_h__
#include "common.h"
#include "buffer.h"
int git_branch_upstream__name(
git_buf *tracking_name,
git_repository *repo,
const char *canonical_branch_name);
#endif
+316
View File
@@ -0,0 +1,316 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "buf_text.h"
int git_buf_text_puts_escaped(
git_buf *buf,
const char *string,
const char *esc_chars,
const char *esc_with)
{
const char *scan;
size_t total = 0, esc_len = strlen(esc_with), count, alloclen;
if (!string)
return 0;
for (scan = string; *scan; ) {
/* count run of non-escaped characters */
count = strcspn(scan, esc_chars);
total += count;
scan += count;
/* count run of escaped characters */
count = strspn(scan, esc_chars);
total += count * (esc_len + 1);
scan += count;
}
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, total, 1);
if (git_buf_grow_by(buf, alloclen) < 0)
return -1;
for (scan = string; *scan; ) {
count = strcspn(scan, esc_chars);
memmove(buf->ptr + buf->size, scan, count);
scan += count;
buf->size += count;
for (count = strspn(scan, esc_chars); count > 0; --count) {
/* copy escape sequence */
memmove(buf->ptr + buf->size, esc_with, esc_len);
buf->size += esc_len;
/* copy character to be escaped */
buf->ptr[buf->size] = *scan;
buf->size++;
scan++;
}
}
buf->ptr[buf->size] = '\0';
return 0;
}
void git_buf_text_unescape(git_buf *buf)
{
buf->size = git__unescape(buf->ptr);
}
int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
{
const char *scan = src->ptr;
const char *scan_end = src->ptr + src->size;
const char *next = memchr(scan, '\r', src->size);
size_t new_size;
char *out;
assert(tgt != src);
if (!next)
return git_buf_set(tgt, src->ptr, src->size);
/* reduce reallocs while in the loop */
GIT_ERROR_CHECK_ALLOC_ADD(&new_size, src->size, 1);
if (git_buf_grow(tgt, new_size) < 0)
return -1;
out = tgt->ptr;
tgt->size = 0;
/* Find the next \r and copy whole chunk up to there to tgt */
for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
if (next > scan) {
size_t copylen = (size_t)(next - scan);
memcpy(out, scan, copylen);
out += copylen;
}
/* Do not drop \r unless it is followed by \n */
if (next + 1 == scan_end || next[1] != '\n')
*out++ = '\r';
}
/* Copy remaining input into dest */
if (scan < scan_end) {
size_t remaining = (size_t)(scan_end - scan);
memcpy(out, scan, remaining);
out += remaining;
}
tgt->size = (size_t)(out - tgt->ptr);
tgt->ptr[tgt->size] = '\0';
return 0;
}
int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
{
const char *start = src->ptr;
const char *end = start + src->size;
const char *scan = start;
const char *next = memchr(scan, '\n', src->size);
size_t alloclen;
assert(tgt != src);
if (!next)
return git_buf_set(tgt, src->ptr, src->size);
/* attempt to reduce reallocs while in the loop */
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, src->size, src->size >> 4);
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
if (git_buf_grow(tgt, alloclen) < 0)
return -1;
tgt->size = 0;
for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
size_t copylen = next - scan;
/* if we find mixed line endings, carry on */
if (copylen && next[-1] == '\r')
copylen--;
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, copylen, 3);
if (git_buf_grow_by(tgt, alloclen) < 0)
return -1;
if (copylen) {
memcpy(tgt->ptr + tgt->size, scan, copylen);
tgt->size += copylen;
}
tgt->ptr[tgt->size++] = '\r';
tgt->ptr[tgt->size++] = '\n';
}
tgt->ptr[tgt->size] = '\0';
return git_buf_put(tgt, scan, end - scan);
}
int git_buf_text_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_text_is_binary(const git_buf *buf)
{
const char *scan = buf->ptr, *end = buf->ptr + buf->size;
git_bom_t bom;
int printable = 0, nonprintable = 0;
scan += git_buf_text_detect_bom(&bom, buf);
if (bom > GIT_BOM_UTF8)
return 1;
while (scan < end) {
unsigned char c = *scan++;
/* Printable characters are those above SPACE (0x1F) excluding DEL,
* and including BS, ESC and FF.
*/
if ((c > 0x1F && c != 127) || c == '\b' || c == '\033' || c == '\014')
printable++;
else if (c == '\0')
return true;
else if (!git__isspace(c))
nonprintable++;
}
return ((printable >> 7) < nonprintable);
}
bool git_buf_text_contains_nul(const git_buf *buf)
{
return (memchr(buf->ptr, '\0', buf->size) != NULL);
}
int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf)
{
const char *ptr;
size_t len;
*bom = GIT_BOM_NONE;
/* need at least 2 bytes to look for any BOM */
if (buf->size < 2)
return 0;
ptr = buf->ptr;
len = buf->size;
switch (*ptr++) {
case 0:
if (len >= 4 && ptr[0] == 0 && ptr[1] == '\xFE' && ptr[2] == '\xFF') {
*bom = GIT_BOM_UTF32_BE;
return 4;
}
break;
case '\xEF':
if (len >= 3 && ptr[0] == '\xBB' && ptr[1] == '\xBF') {
*bom = GIT_BOM_UTF8;
return 3;
}
break;
case '\xFE':
if (*ptr == '\xFF') {
*bom = GIT_BOM_UTF16_BE;
return 2;
}
break;
case '\xFF':
if (*ptr != '\xFE')
break;
if (len >= 4 && ptr[1] == 0 && ptr[2] == 0) {
*bom = GIT_BOM_UTF32_LE;
return 4;
} else {
*bom = GIT_BOM_UTF16_LE;
return 2;
}
break;
default:
break;
}
return 0;
}
bool git_buf_text_gather_stats(
git_buf_text_stats *stats, const git_buf *buf, bool skip_bom)
{
const char *scan = buf->ptr, *end = buf->ptr + buf->size;
int skip;
memset(stats, 0, sizeof(*stats));
/* BOM detection */
skip = git_buf_text_detect_bom(&stats->bom, buf);
if (skip_bom)
scan += skip;
/* Ignore EOF character */
if (buf->size > 0 && end[-1] == '\032')
end--;
/* Counting loop */
while (scan < end) {
unsigned char c = *scan++;
if (c > 0x1F && c != 0x7F)
stats->printable++;
else switch (c) {
case '\0':
stats->nul++;
stats->nonprintable++;
break;
case '\n':
stats->lf++;
break;
case '\r':
stats->cr++;
if (scan < end && *scan == '\n')
stats->crlf++;
break;
case '\t': case '\f': case '\v': case '\b': case 0x1b: /*ESC*/
stats->printable++;
break;
default:
stats->nonprintable++;
break;
}
}
/* Treat files with a bare CR as binary */
return (stats->cr != stats->crlf || stats->nul > 0 ||
((stats->printable >> 7) < stats->nonprintable));
}
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_buf_text_h__
#define INCLUDE_buf_text_h__
#include "common.h"
#include "buffer.h"
typedef enum {
GIT_BOM_NONE = 0,
GIT_BOM_UTF8 = 1,
GIT_BOM_UTF16_LE = 2,
GIT_BOM_UTF16_BE = 3,
GIT_BOM_UTF32_LE = 4,
GIT_BOM_UTF32_BE = 5
} git_bom_t;
typedef struct {
git_bom_t bom; /* BOM found at head of text */
unsigned int nul, cr, lf, crlf; /* NUL, CR, LF and CRLF counts */
unsigned int printable, nonprintable; /* These are just approximations! */
} git_buf_text_stats;
/**
* Append string to buffer, prefixing each character from `esc_chars` with
* `esc_with` string.
*
* @param buf Buffer to append data to
* @param string String to escape and append
* @param esc_chars Characters to be escaped
* @param esc_with String to insert in from of each found character
* @return 0 on success, <0 on failure (probably allocation problem)
*/
extern int git_buf_text_puts_escaped(
git_buf *buf,
const char *string,
const char *esc_chars,
const char *esc_with);
/**
* Append string escaping characters that are regex special
*/
GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
{
return git_buf_text_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\");
}
/**
* Unescape all characters in a buffer in place
*
* I.e. remove backslashes
*/
extern void git_buf_text_unescape(git_buf *buf);
/**
* Replace all \r\n with \n.
*
* @return 0 on success, -1 on memory error
*/
extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
/**
* Replace all \n with \r\n. Does not modify existing \r\n.
*
* @return 0 on success, -1 on memory error
*/
extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
/**
* Fill buffer with the common prefix of a array of strings
*
* Buffer will be set to empty if there is no common prefix
*/
extern int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strs);
/**
* Check quickly if buffer looks like it contains binary data
*
* @param buf Buffer to check
* @return true if buffer looks like non-text data
*/
extern bool git_buf_text_is_binary(const git_buf *buf);
/**
* Check quickly if buffer contains a NUL byte
*
* @param buf Buffer to check
* @return true if buffer contains a NUL byte
*/
extern bool git_buf_text_contains_nul(const git_buf *buf);
/**
* Check if a buffer begins with a UTF BOM
*
* @param bom Set to the type of BOM detected or GIT_BOM_NONE
* @param buf Buffer in which to check the first bytes for a BOM
* @return Number of bytes of BOM data (or 0 if no BOM found)
*/
extern int git_buf_text_detect_bom(git_bom_t *bom, const git_buf *buf);
/**
* Gather stats for a piece of text
*
* Fill the `stats` structure with counts of unreadable characters, carriage
* returns, etc, so it can be used in heuristics. This automatically skips
* a trailing EOF (\032 character). Also it will look for a BOM at the
* start of the text and can be told to skip that as well.
*
* @param stats Structure to be filled in
* @param buf Text to process
* @param skip_bom Exclude leading BOM from stats if true
* @return Does the buffer heuristically look like binary data
*/
extern bool git_buf_text_gather_stats(
git_buf_text_stats *stats, const git_buf *buf, bool skip_bom);
#endif
+1041
View File
File diff suppressed because it is too large Load Diff
+221
View File
@@ -0,0 +1,221 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_buffer_h__
#define INCLUDE_buffer_h__
#include "common.h"
#include "git2/strarray.h"
#include "git2/buffer.h"
/* typedef struct {
* char *ptr;
* size_t asize, size;
* } git_buf;
*/
extern char git_buf__initbuf[];
extern char git_buf__oom[];
/* Use to initialize buffer structure when git_buf is on stack */
#define GIT_BUF_INIT { git_buf__initbuf, 0, 0 }
GIT_INLINE(bool) git_buf_is_allocated(const git_buf *buf)
{
return (buf->ptr != NULL && buf->asize > 0);
}
/**
* Initialize a git_buf structure.
*
* For the cases where GIT_BUF_INIT cannot be used to do static
* initialization.
*/
extern int git_buf_init(git_buf *buf, size_t initial_size);
/**
* Resize the buffer allocation to make more space.
*
* This will attempt to grow the buffer to accommodate the additional size.
* It is similar to `git_buf_grow`, but performs the new size calculation,
* checking for overflow.
*
* Like `git_buf_grow`, if this is a user-supplied buffer, this will allocate
* a new buffer.
*/
extern int git_buf_grow_by(git_buf *buffer, size_t additional_size);
/**
* Attempt to grow the buffer to hold at least `target_size` bytes.
*
* If the allocation fails, this will return an error. If `mark_oom` is true,
* this will mark the buffer as invalid for future operations; if false,
* existing buffer content will be preserved, but calling code must handle
* that buffer was not expanded. If `preserve_external` is true, then any
* existing data pointed to be `ptr` even if `asize` is zero will be copied
* into the newly allocated buffer.
*/
extern int git_buf_try_grow(
git_buf *buf, size_t target_size, bool mark_oom);
/**
* Sanitizes git_buf structures provided from user input. Users of the
* library, when providing git_buf's, may wish to provide a NULL ptr for
* ease of handling. The buffer routines, however, expect a non-NULL ptr
* always. This helper method simply handles NULL input, converting to a
* git_buf__initbuf. If a buffer with a non-NULL ptr is passed in, this method
* assures that the buffer is '\0'-terminated.
*/
extern void git_buf_sanitize(git_buf *buf);
extern void git_buf_swap(git_buf *buf_a, git_buf *buf_b);
extern char *git_buf_detach(git_buf *buf);
extern int git_buf_attach(git_buf *buf, char *ptr, size_t asize);
/* Populates a `git_buf` where the contents are not "owned" by the
* buffer, and calls to `git_buf_dispose` will not free the given buf.
*/
extern void git_buf_attach_notowned(
git_buf *buf, const char *ptr, size_t size);
/**
* 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_sets(git_buf *buf, const char *string);
int git_buf_putc(git_buf *buf, char c);
int git_buf_putcn(git_buf *buf, char c, size_t len);
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_shorten(git_buf *buf, size_t amount);
void git_buf_rtruncate_at_char(git_buf *path, char separator);
/** General join with separator */
int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...);
/** Fast join of two strings - first may legally point into `buf` data */
int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b);
/** Fast join of three strings - cannot reference `buf` data */
int git_buf_join3(git_buf *buf, char separator, const char *str_a, const char *str_b, const char *str_c);
/**
* 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(const 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;
}
GIT_INLINE(ssize_t) git_buf_rfind(const git_buf *buf, char ch)
{
ssize_t idx = (ssize_t)buf->size - 1;
while (idx >= 0 && buf->ptr[idx] != ch) idx--;
return idx;
}
GIT_INLINE(ssize_t) git_buf_find(const git_buf *buf, char ch)
{
void *found = memchr(buf->ptr, ch, buf->size);
return found ? (ssize_t)((const char *)found - buf->ptr) : -1;
}
/* 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);
/* Quote and unquote a buffer as specified in
* http://marc.info/?l=git&m=112927316408690&w=2
*/
int git_buf_quote(git_buf *buf);
int git_buf_unquote(git_buf *buf);
/* Write data as base64 encoded in buffer */
int git_buf_encode_base64(git_buf *buf, const char *data, size_t len);
/* Decode the given bas64 and write the result to the buffer */
int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len);
/* Write data as "base85" encoded in buffer */
int git_buf_encode_base85(git_buf *buf, const char *data, size_t len);
/* Decode the given "base85" and write the result to the buffer */
int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len);
/* Decode the given percent-encoded string and write the result to the buffer */
int git_buf_decode_percent(git_buf *buf, const char *str, size_t len);
/*
* Insert, remove or replace a portion of the buffer.
*
* @param buf The buffer to work with
*
* @param where The location in the buffer where the transformation
* should be applied.
*
* @param nb_to_remove The number of chars to be removed. 0 to not
* remove any character in the buffer.
*
* @param data A pointer to the data which should be inserted.
*
* @param nb_to_insert The number of chars to be inserted. 0 to not
* insert any character from the buffer.
*
* @return 0 or an error code.
*/
int git_buf_splice(
git_buf *buf,
size_t where,
size_t nb_to_remove,
const char *data,
size_t nb_to_insert);
#endif
+270
View File
@@ -0,0 +1,270 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "cache.h"
#include "repository.h"
#include "commit.h"
#include "thread-utils.h"
#include "util.h"
#include "odb.h"
#include "object.h"
#include "git2/oid.h"
bool git_cache__enabled = true;
ssize_t git_cache__max_storage = (256 * 1024 * 1024);
git_atomic_ssize git_cache__current_storage = {0};
static size_t git_cache__max_object_size[8] = {
0, /* GIT_OBJECT__EXT1 */
4096, /* GIT_OBJECT_COMMIT */
4096, /* GIT_OBJECT_TREE */
0, /* GIT_OBJECT_BLOB */
4096, /* GIT_OBJECT_TAG */
0, /* GIT_OBJECT__EXT2 */
0, /* GIT_OBJECT_OFS_DELTA */
0 /* GIT_OBJECT_REF_DELTA */
};
int git_cache_set_max_object_size(git_object_t type, size_t size)
{
if (type < 0 || (size_t)type >= ARRAY_SIZE(git_cache__max_object_size)) {
git_error_set(GIT_ERROR_INVALID, "type out of range");
return -1;
}
git_cache__max_object_size[type] = size;
return 0;
}
void git_cache_dump_stats(git_cache *cache)
{
git_cached_obj *object;
if (git_cache_size(cache) == 0)
return;
printf("Cache %p: %"PRIuZ" items cached, %"PRIdZ" bytes\n",
cache, git_cache_size(cache), cache->used_memory);
git_oidmap_foreach_value(cache->map, object, {
char oid_str[9];
printf(" %s%c %s (%"PRIuZ")\n",
git_object_type2string(object->type),
object->flags == GIT_CACHE_STORE_PARSED ? '*' : ' ',
git_oid_tostr(oid_str, sizeof(oid_str), &object->oid),
object->size
);
});
}
int git_cache_init(git_cache *cache)
{
memset(cache, 0, sizeof(*cache));
if ((git_oidmap_new(&cache->map)) < 0)
return -1;
if (git_rwlock_init(&cache->lock)) {
git_error_set(GIT_ERROR_OS, "failed to initialize cache rwlock");
return -1;
}
return 0;
}
/* called with lock */
static void clear_cache(git_cache *cache)
{
git_cached_obj *evict = NULL;
if (git_cache_size(cache) == 0)
return;
git_oidmap_foreach_value(cache->map, evict, {
git_cached_obj_decref(evict);
});
git_oidmap_clear(cache->map);
git_atomic_ssize_add(&git_cache__current_storage, -cache->used_memory);
cache->used_memory = 0;
}
void git_cache_clear(git_cache *cache)
{
if (git_rwlock_wrlock(&cache->lock) < 0)
return;
clear_cache(cache);
git_rwlock_wrunlock(&cache->lock);
}
void git_cache_dispose(git_cache *cache)
{
git_cache_clear(cache);
git_oidmap_free(cache->map);
git_rwlock_free(&cache->lock);
git__memzero(cache, sizeof(*cache));
}
/* Called with lock */
static void cache_evict_entries(git_cache *cache)
{
size_t evict_count = git_cache_size(cache) / 2048, i;
ssize_t evicted_memory = 0;
if (evict_count < 8)
evict_count = 8;
/* do not infinite loop if there's not enough entries to evict */
if (evict_count > git_cache_size(cache)) {
clear_cache(cache);
return;
}
i = 0;
while (evict_count > 0) {
git_cached_obj *evict;
const git_oid *key;
if (git_oidmap_iterate((void **) &evict, cache->map, &i, &key) == GIT_ITEROVER)
break;
evict_count--;
evicted_memory += evict->size;
git_oidmap_delete(cache->map, key);
git_cached_obj_decref(evict);
}
cache->used_memory -= evicted_memory;
git_atomic_ssize_add(&git_cache__current_storage, -evicted_memory);
}
static bool cache_should_store(git_object_t object_type, size_t object_size)
{
size_t max_size = git_cache__max_object_size[object_type];
return git_cache__enabled && object_size < max_size;
}
static void *cache_get(git_cache *cache, const git_oid *oid, unsigned int flags)
{
git_cached_obj *entry;
if (!git_cache__enabled || git_rwlock_rdlock(&cache->lock) < 0)
return NULL;
if ((entry = git_oidmap_get(cache->map, oid)) != NULL) {
if (flags && entry->flags != flags) {
entry = NULL;
} else {
git_cached_obj_incref(entry);
}
}
git_rwlock_rdunlock(&cache->lock);
return entry;
}
static void *cache_store(git_cache *cache, git_cached_obj *entry)
{
git_cached_obj *stored_entry;
git_cached_obj_incref(entry);
if (!git_cache__enabled && cache->used_memory > 0) {
git_cache_clear(cache);
return entry;
}
if (!cache_should_store(entry->type, entry->size))
return entry;
if (git_rwlock_wrlock(&cache->lock) < 0)
return entry;
/* soften the load on the cache */
if (git_cache__current_storage.val > git_cache__max_storage)
cache_evict_entries(cache);
/* not found */
if ((stored_entry = git_oidmap_get(cache->map, &entry->oid)) == NULL) {
if (git_oidmap_set(cache->map, &entry->oid, entry) == 0) {
git_cached_obj_incref(entry);
cache->used_memory += entry->size;
git_atomic_ssize_add(&git_cache__current_storage, (ssize_t)entry->size);
}
}
/* found */
else {
if (stored_entry->flags == entry->flags) {
git_cached_obj_decref(entry);
git_cached_obj_incref(stored_entry);
entry = stored_entry;
} else if (stored_entry->flags == GIT_CACHE_STORE_RAW &&
entry->flags == GIT_CACHE_STORE_PARSED) {
git_cached_obj_decref(stored_entry);
git_cached_obj_incref(entry);
git_oidmap_set(cache->map, &entry->oid, entry);
} else {
/* NO OP */
}
}
git_rwlock_wrunlock(&cache->lock);
return entry;
}
void *git_cache_store_raw(git_cache *cache, git_odb_object *entry)
{
entry->cached.flags = GIT_CACHE_STORE_RAW;
return cache_store(cache, (git_cached_obj *)entry);
}
void *git_cache_store_parsed(git_cache *cache, git_object *entry)
{
entry->cached.flags = GIT_CACHE_STORE_PARSED;
return cache_store(cache, (git_cached_obj *)entry);
}
git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid)
{
return cache_get(cache, oid, GIT_CACHE_STORE_RAW);
}
git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid)
{
return cache_get(cache, oid, GIT_CACHE_STORE_PARSED);
}
void *git_cache_get_any(git_cache *cache, const git_oid *oid)
{
return cache_get(cache, oid, GIT_CACHE_STORE_ANY);
}
void git_cached_obj_decref(void *_obj)
{
git_cached_obj *obj = _obj;
if (git_atomic_dec(&obj->refcount) == 0) {
switch (obj->flags) {
case GIT_CACHE_STORE_RAW:
git_odb_object__free(_obj);
break;
case GIT_CACHE_STORE_PARSED:
git_object__free(_obj);
break;
default:
git__free(_obj);
break;
}
}
}
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_cache_h__
#define INCLUDE_cache_h__
#include "common.h"
#include "git2/common.h"
#include "git2/oid.h"
#include "git2/odb.h"
#include "thread-utils.h"
#include "oidmap.h"
enum {
GIT_CACHE_STORE_ANY = 0,
GIT_CACHE_STORE_RAW = 1,
GIT_CACHE_STORE_PARSED = 2
};
typedef struct {
git_oid oid;
int16_t type; /* git_object_t value */
uint16_t flags; /* GIT_CACHE_STORE value */
size_t size;
git_atomic refcount;
} git_cached_obj;
typedef struct {
git_oidmap *map;
git_rwlock lock;
ssize_t used_memory;
} git_cache;
extern bool git_cache__enabled;
extern ssize_t git_cache__max_storage;
extern git_atomic_ssize git_cache__current_storage;
int git_cache_set_max_object_size(git_object_t type, size_t size);
int git_cache_init(git_cache *cache);
void git_cache_dispose(git_cache *cache);
void git_cache_clear(git_cache *cache);
void *git_cache_store_raw(git_cache *cache, git_odb_object *entry);
void *git_cache_store_parsed(git_cache *cache, git_object *entry);
git_odb_object *git_cache_get_raw(git_cache *cache, const git_oid *oid);
git_object *git_cache_get_parsed(git_cache *cache, const git_oid *oid);
void *git_cache_get_any(git_cache *cache, const git_oid *oid);
GIT_INLINE(size_t) git_cache_size(git_cache *cache)
{
return (size_t)git_oidmap_size(cache->map);
}
GIT_INLINE(void) git_cached_obj_incref(void *_obj)
{
git_cached_obj *obj = _obj;
git_atomic_inc(&obj->refcount);
}
void git_cached_obj_decref(void *_obj);
#endif
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_cc_compat_h__
#define INCLUDE_cc_compat_h__
#include <stdarg.h>
/*
* 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
/* Default to safer but a bit wasteful traditional style */
# ifndef GIT_FLEX_ARRAY
# define GIT_FLEX_ARRAY 1
# endif
#endif
#ifdef __GNUC__
# define GIT_TYPEOF(x) (__typeof__(x))
#else
# define GIT_TYPEOF(x)
#endif
#if defined(__GNUC__)
# define GIT_ALIGN(x,size) x __attribute__ ((aligned(size)))
#elif defined(_MSC_VER)
# define GIT_ALIGN(x,size) __declspec(align(size)) x
#else
# define GIT_ALIGN(x,size) x
#endif
#define GIT_UNUSED(x) ((void)(x))
/* Define the printf format specifer to use for size_t output */
#if defined(_MSC_VER) || defined(__MINGW32__)
/* Visual Studio 2012 and prior lack PRId64 entirely */
# ifndef PRId64
# define PRId64 "I64d"
# endif
/* The first block is needed to avoid warnings on MingW amd64 */
# if (SIZE_MAX == ULLONG_MAX)
# define PRIuZ "I64u"
# define PRIxZ "I64x"
# define PRIXZ "I64X"
# define PRIdZ "I64d"
# else
# define PRIuZ "Iu"
# define PRIxZ "Ix"
# define PRIXZ "IX"
# define PRIdZ "Id"
# endif
#else
# define PRIuZ "zu"
# define PRIxZ "zx"
# define PRIXZ "zX"
# define PRIdZ "zd"
#endif
/* Micosoft Visual C/C++ */
#if defined(_MSC_VER)
/* disable "deprecated function" warnings */
# pragma warning ( disable : 4996 )
/* disable "conditional expression is constant" level 4 warnings */
# pragma warning ( disable : 4127 )
#endif
#if defined (_MSC_VER)
typedef unsigned char bool;
# ifndef true
# define true 1
# endif
# ifndef false
# define false 0
# endif
#else
# include <stdbool.h>
#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
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_checkout_h__
#define INCLUDE_checkout_h__
#include "common.h"
#include "git2/checkout.h"
#include "iterator.h"
#define GIT_CHECKOUT__NOTIFY_CONFLICT_TREE (1u << 12)
/**
* Update the working directory to match the target iterator. The
* expected baseline value can be passed in via the checkout options
* or else will default to the HEAD commit.
*/
extern int git_checkout_iterator(
git_iterator *target,
git_index *index,
const git_checkout_options *opts);
#endif
+236
View File
@@ -0,0 +1,236 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "repository.h"
#include "filebuf.h"
#include "merge.h"
#include "vector.h"
#include "index.h"
#include "git2/types.h"
#include "git2/merge.h"
#include "git2/cherrypick.h"
#include "git2/commit.h"
#include "git2/sys/commit.h"
#define GIT_CHERRYPICK_FILE_MODE 0666
static int write_cherrypick_head(
git_repository *repo,
const char *commit_oidstr)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf file_path = GIT_BUF_INIT;
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_CHERRYPICK_HEAD_FILE)) >= 0 &&
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) >= 0 &&
(error = git_filebuf_printf(&file, "%s\n", commit_oidstr)) >= 0)
error = git_filebuf_commit(&file);
if (error < 0)
git_filebuf_cleanup(&file);
git_buf_dispose(&file_path);
return error;
}
static int write_merge_msg(
git_repository *repo,
const char *commit_msg)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf file_path = GIT_BUF_INIT;
int error = 0;
if ((error = git_buf_joinpath(&file_path, repo->gitdir, GIT_MERGE_MSG_FILE)) < 0 ||
(error = git_filebuf_open(&file, file_path.ptr, GIT_FILEBUF_CREATE_LEADING_DIRS, GIT_CHERRYPICK_FILE_MODE)) < 0 ||
(error = git_filebuf_printf(&file, "%s", commit_msg)) < 0)
goto cleanup;
error = git_filebuf_commit(&file);
cleanup:
if (error < 0)
git_filebuf_cleanup(&file);
git_buf_dispose(&file_path);
return error;
}
static int cherrypick_normalize_opts(
git_repository *repo,
git_cherrypick_options *opts,
const git_cherrypick_options *given,
const char *their_label)
{
int error = 0;
unsigned int default_checkout_strategy = GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_ALLOW_CONFLICTS;
GIT_UNUSED(repo);
if (given != NULL)
memcpy(opts, given, sizeof(git_cherrypick_options));
else {
git_cherrypick_options default_opts = GIT_CHERRYPICK_OPTIONS_INIT;
memcpy(opts, &default_opts, sizeof(git_cherrypick_options));
}
if (!opts->checkout_opts.checkout_strategy)
opts->checkout_opts.checkout_strategy = default_checkout_strategy;
if (!opts->checkout_opts.our_label)
opts->checkout_opts.our_label = "HEAD";
if (!opts->checkout_opts.their_label)
opts->checkout_opts.their_label = their_label;
return error;
}
static int cherrypick_state_cleanup(git_repository *repo)
{
const char *state_files[] = { GIT_CHERRYPICK_HEAD_FILE, GIT_MERGE_MSG_FILE };
return git_repository__cleanup_files(repo, state_files, ARRAY_SIZE(state_files));
}
static int cherrypick_seterr(git_commit *commit, const char *fmt)
{
char commit_oidstr[GIT_OID_HEXSZ + 1];
git_error_set(GIT_ERROR_CHERRYPICK, fmt,
git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit)));
return -1;
}
int git_cherrypick_commit(
git_index **out,
git_repository *repo,
git_commit *cherrypick_commit,
git_commit *our_commit,
unsigned int mainline,
const git_merge_options *merge_opts)
{
git_commit *parent_commit = NULL;
git_tree *parent_tree = NULL, *our_tree = NULL, *cherrypick_tree = NULL;
int parent = 0, error = 0;
assert(out && repo && cherrypick_commit && our_commit);
if (git_commit_parentcount(cherrypick_commit) > 1) {
if (!mainline)
return cherrypick_seterr(cherrypick_commit,
"mainline branch is not specified but %s is a merge commit");
parent = mainline;
} else {
if (mainline)
return cherrypick_seterr(cherrypick_commit,
"mainline branch specified but %s is not a merge commit");
parent = git_commit_parentcount(cherrypick_commit);
}
if (parent &&
((error = git_commit_parent(&parent_commit, cherrypick_commit, (parent - 1))) < 0 ||
(error = git_commit_tree(&parent_tree, parent_commit)) < 0))
goto done;
if ((error = git_commit_tree(&cherrypick_tree, cherrypick_commit)) < 0 ||
(error = git_commit_tree(&our_tree, our_commit)) < 0)
goto done;
error = git_merge_trees(out, repo, parent_tree, our_tree, cherrypick_tree, merge_opts);
done:
git_tree_free(parent_tree);
git_tree_free(our_tree);
git_tree_free(cherrypick_tree);
git_commit_free(parent_commit);
return error;
}
int git_cherrypick(
git_repository *repo,
git_commit *commit,
const git_cherrypick_options *given_opts)
{
git_cherrypick_options opts;
git_reference *our_ref = NULL;
git_commit *our_commit = NULL;
char commit_oidstr[GIT_OID_HEXSZ + 1];
const char *commit_msg, *commit_summary;
git_buf their_label = GIT_BUF_INIT;
git_index *index = NULL;
git_indexwriter indexwriter = GIT_INDEXWRITER_INIT;
int error = 0;
assert(repo && commit);
GIT_ERROR_CHECK_VERSION(given_opts, GIT_CHERRYPICK_OPTIONS_VERSION, "git_cherrypick_options");
if ((error = git_repository__ensure_not_bare(repo, "cherry-pick")) < 0)
return error;
if ((commit_msg = git_commit_message(commit)) == NULL ||
(commit_summary = git_commit_summary(commit)) == NULL) {
error = -1;
goto on_error;
}
git_oid_nfmt(commit_oidstr, sizeof(commit_oidstr), git_commit_id(commit));
if ((error = write_merge_msg(repo, commit_msg)) < 0 ||
(error = git_buf_printf(&their_label, "%.7s... %s", commit_oidstr, commit_summary)) < 0 ||
(error = cherrypick_normalize_opts(repo, &opts, given_opts, git_buf_cstr(&their_label))) < 0 ||
(error = git_indexwriter_init_for_operation(&indexwriter, repo, &opts.checkout_opts.checkout_strategy)) < 0 ||
(error = write_cherrypick_head(repo, commit_oidstr)) < 0 ||
(error = git_repository_head(&our_ref, repo)) < 0 ||
(error = git_reference_peel((git_object **)&our_commit, our_ref, GIT_OBJECT_COMMIT)) < 0 ||
(error = git_cherrypick_commit(&index, repo, commit, our_commit, opts.mainline, &opts.merge_opts)) < 0 ||
(error = git_merge__check_result(repo, index)) < 0 ||
(error = git_merge__append_conflicts_to_merge_msg(repo, index)) < 0 ||
(error = git_checkout_index(repo, index, &opts.checkout_opts)) < 0 ||
(error = git_indexwriter_commit(&indexwriter)) < 0)
goto on_error;
goto done;
on_error:
cherrypick_state_cleanup(repo);
done:
git_indexwriter_cleanup(&indexwriter);
git_index_free(index);
git_commit_free(our_commit);
git_reference_free(our_ref);
git_buf_dispose(&their_label);
return error;
}
int git_cherrypick_options_init(
git_cherrypick_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_cherrypick_options, GIT_CHERRYPICK_OPTIONS_INIT);
return 0;
}
int git_cherrypick_init_options(
git_cherrypick_options *opts, unsigned int version)
{
return git_cherrypick_options_init(opts, version);
}
+580
View File
@@ -0,0 +1,580 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "clone.h"
#include "git2/clone.h"
#include "git2/remote.h"
#include "git2/revparse.h"
#include "git2/branch.h"
#include "git2/config.h"
#include "git2/checkout.h"
#include "git2/commit.h"
#include "git2/tree.h"
#include "remote.h"
#include "futils.h"
#include "refs.h"
#include "path.h"
#include "repository.h"
#include "odb.h"
static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link);
static int create_branch(
git_reference **branch,
git_repository *repo,
const git_oid *target,
const char *name,
const char *log_message)
{
git_commit *head_obj = NULL;
git_reference *branch_ref = NULL;
git_buf refname = GIT_BUF_INIT;
int error;
/* Find the target commit */
if ((error = git_commit_lookup(&head_obj, repo, target)) < 0)
return error;
/* Create the new branch */
if ((error = git_buf_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0)
return error;
error = git_reference_create(&branch_ref, repo, git_buf_cstr(&refname), target, 0, log_message);
git_buf_dispose(&refname);
git_commit_free(head_obj);
if (!error)
*branch = branch_ref;
else
git_reference_free(branch_ref);
return error;
}
static int setup_tracking_config(
git_repository *repo,
const char *branch_name,
const char *remote_name,
const char *merge_target)
{
git_config *cfg;
git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT;
int error = -1;
if (git_repository_config__weakptr(&cfg, repo) < 0)
return -1;
if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0)
goto cleanup;
if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0)
goto cleanup;
if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0)
goto cleanup;
if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0)
goto cleanup;
error = 0;
cleanup:
git_buf_dispose(&remote_key);
git_buf_dispose(&merge_key);
return error;
}
static int create_tracking_branch(
git_reference **branch,
git_repository *repo,
const git_oid *target,
const char *branch_name,
const char *log_message)
{
int error;
if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0)
return error;
return setup_tracking_config(
repo,
branch_name,
GIT_REMOTE_ORIGIN,
git_reference_name(*branch));
}
static int update_head_to_new_branch(
git_repository *repo,
const git_oid *target,
const char *name,
const char *reflog_message)
{
git_reference *tracking_branch = NULL;
int error;
if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR))
name += strlen(GIT_REFS_HEADS_DIR);
error = create_tracking_branch(&tracking_branch, repo, target, name,
reflog_message);
if (!error)
error = git_repository_set_head(
repo, git_reference_name(tracking_branch));
git_reference_free(tracking_branch);
/* if it already existed, then the user's refspec created it for us, ignore it' */
if (error == GIT_EEXISTS)
error = 0;
return error;
}
static int update_head_to_remote(
git_repository *repo,
git_remote *remote,
const char *reflog_message)
{
int error = 0;
size_t refs_len;
git_refspec *refspec;
const git_remote_head *remote_head, **refs;
const git_oid *remote_head_id;
git_buf remote_master_name = GIT_BUF_INIT;
git_buf branch = GIT_BUF_INIT;
if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0)
return error;
/* We cloned an empty repository or one with an unborn HEAD */
if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE))
return setup_tracking_config(
repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE);
/* We know we have HEAD, let's see where it points */
remote_head = refs[0];
assert(remote_head);
remote_head_id = &remote_head->oid;
error = git_remote_default_branch(&branch, remote);
if (error == GIT_ENOTFOUND) {
error = git_repository_set_head_detached(
repo, remote_head_id);
goto cleanup;
}
refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch));
if (refspec == NULL) {
git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration");
error = GIT_EINVALIDSPEC;
goto cleanup;
}
/* Determine the remote tracking reference name from the local master */
if ((error = git_refspec_transform(
&remote_master_name,
refspec,
git_buf_cstr(&branch))) < 0)
goto cleanup;
error = update_head_to_new_branch(
repo,
remote_head_id,
git_buf_cstr(&branch),
reflog_message);
cleanup:
git_buf_dispose(&remote_master_name);
git_buf_dispose(&branch);
return error;
}
static int update_head_to_branch(
git_repository *repo,
const char *remote_name,
const char *branch,
const char *reflog_message)
{
int retcode;
git_buf remote_branch_name = GIT_BUF_INIT;
git_reference* remote_ref = NULL;
assert(remote_name && branch);
if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
remote_name, branch)) < 0 )
goto cleanup;
if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
goto cleanup;
retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch,
reflog_message);
cleanup:
git_reference_free(remote_ref);
git_buf_dispose(&remote_branch_name);
return retcode;
}
static int default_repository_create(git_repository **out, const char *path, int bare, void *payload)
{
GIT_UNUSED(payload);
return git_repository_init(out, path, bare);
}
static int default_remote_create(
git_remote **out,
git_repository *repo,
const char *name,
const char *url,
void *payload)
{
GIT_UNUSED(payload);
return git_remote_create(out, repo, name, url);
}
/*
* submodules?
*/
static int create_and_configure_origin(
git_remote **out,
git_repository *repo,
const char *url,
const git_clone_options *options)
{
int error;
git_remote *origin = NULL;
char buf[GIT_PATH_MAX];
git_remote_create_cb remote_create = options->remote_cb;
void *payload = options->remote_cb_payload;
/* If the path exists and is a dir, the url should be the absolute path */
if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) {
if (p_realpath(url, buf) == NULL)
return -1;
url = buf;
}
if (!remote_create) {
remote_create = default_remote_create;
payload = NULL;
}
if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0)
goto on_error;
*out = origin;
return 0;
on_error:
git_remote_free(origin);
return error;
}
static bool should_checkout(
git_repository *repo,
bool is_bare,
const git_checkout_options *opts)
{
if (is_bare)
return false;
if (!opts)
return false;
if (opts->checkout_strategy == GIT_CHECKOUT_NONE)
return false;
return !git_repository_head_unborn(repo);
}
static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message)
{
int error;
if (branch)
error = update_head_to_branch(repo, git_remote_name(remote), branch,
reflog_message);
/* Point HEAD to the same ref as the remote's head */
else
error = update_head_to_remote(repo, remote, reflog_message);
if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
error = git_checkout_head(repo, co_opts);
return error;
}
static int clone_into(git_repository *repo, git_remote *_remote, const git_fetch_options *opts, const git_checkout_options *co_opts, const char *branch)
{
int error;
git_buf reflog_message = GIT_BUF_INIT;
git_fetch_options fetch_opts;
git_remote *remote;
assert(repo && _remote);
if (!git_repository_is_empty(repo)) {
git_error_set(GIT_ERROR_INVALID, "the repository is not empty");
return -1;
}
if ((error = git_remote_dup(&remote, _remote)) < 0)
return error;
memcpy(&fetch_opts, opts, sizeof(git_fetch_options));
fetch_opts.update_fetchhead = 0;
fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL;
git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_buf_cstr(&reflog_message))) != 0)
goto cleanup;
error = checkout_branch(repo, remote, co_opts, branch, git_buf_cstr(&reflog_message));
cleanup:
git_remote_free(remote);
git_buf_dispose(&reflog_message);
return error;
}
int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local)
{
git_buf fromurl = GIT_BUF_INIT;
const char *path = url_or_path;
bool is_url, is_local;
if (local == GIT_CLONE_NO_LOCAL)
return 0;
if ((is_url = git_path_is_local_file_url(url_or_path)) != 0) {
if (git_path_fromurl(&fromurl, url_or_path) < 0) {
is_local = -1;
goto done;
}
path = fromurl.ptr;
}
is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) &&
git_path_isdir(path);
done:
git_buf_dispose(&fromurl);
return is_local;
}
static int git__clone(
git_repository **out,
const char *url,
const char *local_path,
const git_clone_options *_options,
int use_existing)
{
int error = 0;
git_repository *repo = NULL;
git_remote *origin;
git_clone_options options = GIT_CLONE_OPTIONS_INIT;
uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES;
git_repository_create_cb repository_cb;
assert(out && url && local_path);
if (_options)
memcpy(&options, _options, sizeof(git_clone_options));
GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
/* Only clone to a new directory or an empty directory */
if (git_path_exists(local_path) && !use_existing && !git_path_is_empty_dir(local_path)) {
git_error_set(GIT_ERROR_INVALID,
"'%s' exists and is not an empty directory", local_path);
return GIT_EEXISTS;
}
/* Only remove the root directory on failure if we create it */
if (git_path_exists(local_path))
rmdir_flags |= GIT_RMDIR_SKIP_ROOT;
if (options.repository_cb)
repository_cb = options.repository_cb;
else
repository_cb = default_repository_create;
if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0)
return error;
if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
int clone_local = git_clone__should_clone_local(url, options.local);
int link = options.local != GIT_CLONE_LOCAL_NO_LINKS;
if (clone_local == 1)
error = clone_local_into(
repo, origin, &options.fetch_opts, &options.checkout_opts,
options.checkout_branch, link);
else if (clone_local == 0)
error = clone_into(
repo, origin, &options.fetch_opts, &options.checkout_opts,
options.checkout_branch);
else
error = -1;
git_remote_free(origin);
}
if (error != 0) {
git_error_state last_error = {0};
git_error_state_capture(&last_error, error);
git_repository_free(repo);
repo = NULL;
(void)git_futils_rmdir_r(local_path, NULL, rmdir_flags);
git_error_state_restore(&last_error);
}
*out = repo;
return error;
}
int git_clone(
git_repository **out,
const char *url,
const char *local_path,
const git_clone_options *_options)
{
return git__clone(out, url, local_path, _options, 0);
}
int git_clone__submodule(
git_repository **out,
const char *url,
const char *local_path,
const git_clone_options *_options)
{
return git__clone(out, url, local_path, _options, 1);
}
int git_clone_options_init(git_clone_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT);
return 0;
}
int git_clone_init_options(git_clone_options *opts, unsigned int version)
{
return git_clone_options_init(opts, version);
}
static bool can_link(const char *src, const char *dst, int link)
{
#ifdef GIT_WIN32
GIT_UNUSED(src);
GIT_UNUSED(dst);
GIT_UNUSED(link);
return false;
#else
struct stat st_src, st_dst;
if (!link)
return false;
if (p_stat(src, &st_src) < 0)
return false;
if (p_stat(dst, &st_dst) < 0)
return false;
return st_src.st_dev == st_dst.st_dev;
#endif
}
static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link)
{
int error, flags;
git_repository *src;
git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT;
git_buf reflog_message = GIT_BUF_INIT;
assert(repo && remote);
if (!git_repository_is_empty(repo)) {
git_error_set(GIT_ERROR_INVALID, "the repository is not empty");
return -1;
}
/*
* Let's figure out what path we should use for the source
* repo, if it's not rooted, the path should be relative to
* the repository's worktree/gitdir.
*/
if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0)
return error;
/* Copy .git/objects/ from the source to the target */
if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) {
git_buf_dispose(&src_path);
return error;
}
if (git_repository_item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0
|| git_repository_item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) {
error = -1;
goto cleanup;
}
flags = 0;
if (can_link(git_repository_path(src), git_repository_path(repo), link))
flags |= GIT_CPDIR_LINK_FILES;
error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
flags, GIT_OBJECT_DIR_MODE);
/*
* can_link() doesn't catch all variations, so if we hit an
* error and did want to link, let's try again without trying
* to link.
*/
if (error < 0 && link) {
flags &= ~GIT_CPDIR_LINK_FILES;
error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
flags, GIT_OBJECT_DIR_MODE);
}
if (error < 0)
goto cleanup;
git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_buf_cstr(&reflog_message))) != 0)
goto cleanup;
error = checkout_branch(repo, remote, co_opts, branch, git_buf_cstr(&reflog_message));
cleanup:
git_buf_dispose(&reflog_message);
git_buf_dispose(&src_path);
git_buf_dispose(&src_odb);
git_buf_dispose(&dst_odb);
git_repository_free(src);
return error;
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_clone_h__
#define INCLUDE_clone_h__
#include "common.h"
#include "git2/clone.h"
extern int git_clone__submodule(git_repository **out,
const char *url, const char *local_path,
const git_clone_options *_options);
extern int git_clone__should_clone_local(const char *url, git_clone_local_t local);
#endif
+959
View File
@@ -0,0 +1,959 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "commit.h"
#include "git2/common.h"
#include "git2/object.h"
#include "git2/repository.h"
#include "git2/signature.h"
#include "git2/mailmap.h"
#include "git2/sys/commit.h"
#include "odb.h"
#include "commit.h"
#include "signature.h"
#include "message.h"
#include "refs.h"
#include "object.h"
#include "array.h"
#include "oidarray.h"
void git_commit__free(void *_commit)
{
git_commit *commit = _commit;
git_array_clear(commit->parent_ids);
git_signature_free(commit->author);
git_signature_free(commit->committer);
git__free(commit->raw_header);
git__free(commit->raw_message);
git__free(commit->message_encoding);
git__free(commit->summary);
git__free(commit->body);
git__free(commit);
}
static int git_commit__create_buffer_internal(
git_buf *out,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_array_oid_t *parents)
{
size_t i = 0;
const git_oid *parent;
assert(out && tree);
git_oid__writebuf(out, "tree ", tree);
for (i = 0; i < git_array_size(*parents); i++) {
parent = git_array_get(*parents, i);
git_oid__writebuf(out, "parent ", parent);
}
git_signature__writebuf(out, "author ", author);
git_signature__writebuf(out, "committer ", committer);
if (message_encoding != NULL)
git_buf_printf(out, "encoding %s\n", message_encoding);
git_buf_putc(out, '\n');
if (git_buf_puts(out, message) < 0)
goto on_error;
return 0;
on_error:
git_buf_dispose(out);
return -1;
}
static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree,
git_commit_parent_callback parent_cb, void *parent_payload,
const git_oid *current_id, bool validate)
{
size_t i;
int error;
git_oid *parent_cpy;
const git_oid *parent;
if (validate && !git_object__is_valid(repo, tree, GIT_OBJECT_TREE))
return -1;
i = 0;
while ((parent = parent_cb(i, parent_payload)) != NULL) {
if (validate && !git_object__is_valid(repo, parent, GIT_OBJECT_COMMIT)) {
error = -1;
goto on_error;
}
parent_cpy = git_array_alloc(*parents);
GIT_ERROR_CHECK_ALLOC(parent_cpy);
git_oid_cpy(parent_cpy, parent);
i++;
}
if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) {
git_error_set(GIT_ERROR_OBJECT, "failed to create commit: current tip is not the first parent");
error = GIT_EMODIFIED;
goto on_error;
}
return 0;
on_error:
git_array_clear(*parents);
return error;
}
static int git_commit__create_internal(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_commit_parent_callback parent_cb,
void *parent_payload,
bool validate)
{
int error;
git_odb *odb;
git_reference *ref = NULL;
git_buf buf = GIT_BUF_INIT;
const git_oid *current_id = NULL;
git_array_oid_t parents = GIT_ARRAY_INIT;
if (update_ref) {
error = git_reference_lookup_resolved(&ref, repo, update_ref, 10);
if (error < 0 && error != GIT_ENOTFOUND)
return error;
}
git_error_clear();
if (ref)
current_id = git_reference_target(ref);
if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0)
goto cleanup;
error = git_commit__create_buffer_internal(&buf, author, committer,
message_encoding, message, tree,
&parents);
if (error < 0)
goto cleanup;
if (git_repository_odb__weakptr(&odb, repo) < 0)
goto cleanup;
if (git_odb__freshen(odb, tree) < 0)
goto cleanup;
if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJECT_COMMIT) < 0)
goto cleanup;
if (update_ref != NULL) {
error = git_reference__update_for_commit(
repo, ref, update_ref, id, "commit");
goto cleanup;
}
cleanup:
git_array_clear(parents);
git_reference_free(ref);
git_buf_dispose(&buf);
return error;
}
int git_commit_create_from_callback(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
git_commit_parent_callback parent_cb,
void *parent_payload)
{
return git_commit__create_internal(
id, repo, update_ref, author, committer, message_encoding, message,
tree, parent_cb, parent_payload, true);
}
typedef struct {
size_t total;
va_list args;
} commit_parent_varargs;
static const git_oid *commit_parent_from_varargs(size_t curr, void *payload)
{
commit_parent_varargs *data = payload;
const git_commit *commit;
if (curr >= data->total)
return NULL;
commit = va_arg(data->args, const git_commit *);
return commit ? git_commit_id(commit) : NULL;
}
int git_commit_create_v(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_tree *tree,
size_t parent_count,
...)
{
int error = 0;
commit_parent_varargs data;
assert(tree && git_tree_owner(tree) == repo);
data.total = parent_count;
va_start(data.args, parent_count);
error = git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, git_tree_id(tree),
commit_parent_from_varargs, &data, false);
va_end(data.args);
return error;
}
typedef struct {
size_t total;
const git_oid **parents;
} commit_parent_oids;
static const git_oid *commit_parent_from_ids(size_t curr, void *payload)
{
commit_parent_oids *data = payload;
return (curr < data->total) ? data->parents[curr] : NULL;
}
int git_commit_create_from_ids(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_oid *tree,
size_t parent_count,
const git_oid *parents[])
{
commit_parent_oids data = { parent_count, parents };
return git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, tree,
commit_parent_from_ids, &data, true);
}
typedef struct {
size_t total;
const git_commit **parents;
git_repository *repo;
} commit_parent_data;
static const git_oid *commit_parent_from_array(size_t curr, void *payload)
{
commit_parent_data *data = payload;
const git_commit *commit;
if (curr >= data->total)
return NULL;
commit = data->parents[curr];
if (git_commit_owner(commit) != data->repo)
return NULL;
return git_commit_id(commit);
}
int git_commit_create(
git_oid *id,
git_repository *repo,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_tree *tree,
size_t parent_count,
const git_commit *parents[])
{
commit_parent_data data = { parent_count, parents, repo };
assert(tree && git_tree_owner(tree) == repo);
return git_commit__create_internal(
id, repo, update_ref, author, committer,
message_encoding, message, git_tree_id(tree),
commit_parent_from_array, &data, false);
}
static const git_oid *commit_parent_for_amend(size_t curr, void *payload)
{
const git_commit *commit_to_amend = payload;
if (curr >= git_array_size(commit_to_amend->parent_ids))
return NULL;
return git_array_get(commit_to_amend->parent_ids, curr);
}
int git_commit_amend(
git_oid *id,
const git_commit *commit_to_amend,
const char *update_ref,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_tree *tree)
{
git_repository *repo;
git_oid tree_id;
git_reference *ref;
int error;
assert(id && commit_to_amend);
repo = git_commit_owner(commit_to_amend);
if (!author)
author = git_commit_author(commit_to_amend);
if (!committer)
committer = git_commit_committer(commit_to_amend);
if (!message_encoding)
message_encoding = git_commit_message_encoding(commit_to_amend);
if (!message)
message = git_commit_message(commit_to_amend);
if (!tree) {
git_tree *old_tree;
GIT_ERROR_CHECK_ERROR( git_commit_tree(&old_tree, commit_to_amend) );
git_oid_cpy(&tree_id, git_tree_id(old_tree));
git_tree_free(old_tree);
} else {
assert(git_tree_owner(tree) == repo);
git_oid_cpy(&tree_id, git_tree_id(tree));
}
if (update_ref) {
if ((error = git_reference_lookup_resolved(&ref, repo, update_ref, 5)) < 0)
return error;
if (git_oid_cmp(git_commit_id(commit_to_amend), git_reference_target(ref))) {
git_reference_free(ref);
git_error_set(GIT_ERROR_REFERENCE, "commit to amend is not the tip of the given branch");
return -1;
}
}
error = git_commit__create_internal(
id, repo, NULL, author, committer, message_encoding, message,
&tree_id, commit_parent_for_amend, (void *)commit_to_amend, false);
if (!error && update_ref) {
error = git_reference__update_for_commit(
repo, ref, NULL, id, "commit");
git_reference_free(ref);
}
return error;
}
static int commit_parse(git_commit *commit, const char *data, size_t size, unsigned int flags)
{
const char *buffer_start = data, *buffer;
const char *buffer_end = buffer_start + size;
git_oid parent_id;
size_t header_len;
git_signature dummy_sig;
assert(commit && data);
buffer = buffer_start;
/* Allocate for one, which will allow not to realloc 90% of the time */
git_array_init_to_size(commit->parent_ids, 1);
GIT_ERROR_CHECK_ARRAY(commit->parent_ids);
/* The tree is always the first field */
if (!(flags & GIT_COMMIT_PARSE_QUICK)) {
if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0)
goto bad_buffer;
} else {
size_t tree_len = strlen("tree ") + GIT_OID_HEXSZ + 1;
if (buffer + tree_len > buffer_end)
goto bad_buffer;
buffer += tree_len;
}
/*
* TODO: commit grafts!
*/
while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) {
git_oid *new_id = git_array_alloc(commit->parent_ids);
GIT_ERROR_CHECK_ALLOC(new_id);
git_oid_cpy(new_id, &parent_id);
}
if (!(flags & GIT_COMMIT_PARSE_QUICK)) {
commit->author = git__malloc(sizeof(git_signature));
GIT_ERROR_CHECK_ALLOC(commit->author);
if (git_signature__parse(commit->author, &buffer, buffer_end, "author ", '\n') < 0)
return -1;
}
/* Some tools create multiple author fields, ignore the extra ones */
while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) {
if (git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n') < 0)
return -1;
git__free(dummy_sig.name);
git__free(dummy_sig.email);
}
/* Always parse the committer; we need the commit time */
commit->committer = git__malloc(sizeof(git_signature));
GIT_ERROR_CHECK_ALLOC(commit->committer);
if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0)
return -1;
if (flags & GIT_COMMIT_PARSE_QUICK)
return 0;
/* Parse add'l header entries */
while (buffer < buffer_end) {
const char *eoln = buffer;
if (buffer[-1] == '\n' && buffer[0] == '\n')
break;
while (eoln < buffer_end && *eoln != '\n')
++eoln;
if (git__prefixncmp(buffer, buffer_end - buffer, "encoding ") == 0) {
buffer += strlen("encoding ");
commit->message_encoding = git__strndup(buffer, eoln - buffer);
GIT_ERROR_CHECK_ALLOC(commit->message_encoding);
}
if (eoln < buffer_end && *eoln == '\n')
++eoln;
buffer = eoln;
}
header_len = buffer - buffer_start;
commit->raw_header = git__strndup(buffer_start, header_len);
GIT_ERROR_CHECK_ALLOC(commit->raw_header);
/* point "buffer" to data after header, +1 for the final LF */
buffer = buffer_start + header_len + 1;
/* extract commit message */
if (buffer <= buffer_end)
commit->raw_message = git__strndup(buffer, buffer_end - buffer);
else
commit->raw_message = git__strdup("");
GIT_ERROR_CHECK_ALLOC(commit->raw_message);
return 0;
bad_buffer:
git_error_set(GIT_ERROR_OBJECT, "failed to parse bad commit object");
return -1;
}
int git_commit__parse_raw(void *commit, const char *data, size_t size)
{
return commit_parse(commit, data, size, 0);
}
int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags)
{
return commit_parse(commit, git_odb_object_data(odb_obj), git_odb_object_size(odb_obj), flags);
}
int git_commit__parse(void *_commit, git_odb_object *odb_obj)
{
return git_commit__parse_ext(_commit, odb_obj, 0);
}
#define GIT_COMMIT_GETTER(_rvalue, _name, _return) \
_rvalue git_commit_##_name(const git_commit *commit) \
{\
assert(commit); \
return _return; \
}
GIT_COMMIT_GETTER(const git_signature *, author, commit->author)
GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer)
GIT_COMMIT_GETTER(const char *, message_raw, commit->raw_message)
GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding)
GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header)
GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time)
GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset)
GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids))
GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id)
const char *git_commit_message(const git_commit *commit)
{
const char *message;
assert(commit);
message = commit->raw_message;
/* trim leading newlines from raw message */
while (*message && *message == '\n')
++message;
return message;
}
const char *git_commit_summary(git_commit *commit)
{
git_buf summary = GIT_BUF_INIT;
const char *msg, *space;
bool space_contains_newline = false;
assert(commit);
if (!commit->summary) {
for (msg = git_commit_message(commit), space = NULL; *msg; ++msg) {
char next_character = msg[0];
/* stop processing at the end of the first paragraph */
if (next_character == '\n' && (!msg[1] || msg[1] == '\n'))
break;
/* record the beginning of contiguous whitespace runs */
else if (git__isspace(next_character)) {
if(space == NULL) {
space = msg;
space_contains_newline = false;
}
space_contains_newline |= next_character == '\n';
}
/* the next character is non-space */
else {
/* process any recorded whitespace */
if (space) {
if(space_contains_newline)
git_buf_putc(&summary, ' '); /* if the space contains a newline, collapse to ' ' */
else
git_buf_put(&summary, space, (msg - space)); /* otherwise copy it */
space = NULL;
}
/* copy the next character */
git_buf_putc(&summary, next_character);
}
}
commit->summary = git_buf_detach(&summary);
if (!commit->summary)
commit->summary = git__strdup("");
}
return commit->summary;
}
const char *git_commit_body(git_commit *commit)
{
const char *msg, *end;
assert(commit);
if (!commit->body) {
/* search for end of summary */
for (msg = git_commit_message(commit); *msg; ++msg)
if (msg[0] == '\n' && (!msg[1] || msg[1] == '\n'))
break;
/* trim leading and trailing whitespace */
for (; *msg; ++msg)
if (!git__isspace(*msg))
break;
for (end = msg + strlen(msg) - 1; msg <= end; --end)
if (!git__isspace(*end))
break;
if (*msg)
commit->body = git__strndup(msg, end - msg + 1);
}
return commit->body;
}
int git_commit_tree(git_tree **tree_out, const git_commit *commit)
{
assert(commit);
return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_id);
}
const git_oid *git_commit_parent_id(
const git_commit *commit, unsigned int n)
{
assert(commit);
return git_array_get(commit->parent_ids, n);
}
int git_commit_parent(
git_commit **parent, const git_commit *commit, unsigned int n)
{
const git_oid *parent_id;
assert(commit);
parent_id = git_commit_parent_id(commit, n);
if (parent_id == NULL) {
git_error_set(GIT_ERROR_INVALID, "parent %u does not exist", n);
return GIT_ENOTFOUND;
}
return git_commit_lookup(parent, commit->object.repo, parent_id);
}
int git_commit_nth_gen_ancestor(
git_commit **ancestor,
const git_commit *commit,
unsigned int n)
{
git_commit *current, *parent = NULL;
int error;
assert(ancestor && commit);
if (git_commit_dup(&current, (git_commit *)commit) < 0)
return -1;
if (n == 0) {
*ancestor = current;
return 0;
}
while (n--) {
error = git_commit_parent(&parent, current, 0);
git_commit_free(current);
if (error < 0)
return error;
current = parent;
}
*ancestor = parent;
return 0;
}
int git_commit_header_field(git_buf *out, const git_commit *commit, const char *field)
{
const char *eol, *buf = commit->raw_header;
git_buf_clear(out);
while ((eol = strchr(buf, '\n'))) {
/* We can skip continuations here */
if (buf[0] == ' ') {
buf = eol + 1;
continue;
}
/* Skip until we find the field we're after */
if (git__prefixcmp(buf, field)) {
buf = eol + 1;
continue;
}
buf += strlen(field);
/* Check that we're not matching a prefix but the field itself */
if (buf[0] != ' ') {
buf = eol + 1;
continue;
}
buf++; /* skip the SP */
git_buf_put(out, buf, eol - buf);
if (git_buf_oom(out))
goto oom;
/* If the next line starts with SP, it's multi-line, we must continue */
while (eol[1] == ' ') {
git_buf_putc(out, '\n');
buf = eol + 2;
eol = strchr(buf, '\n');
if (!eol)
goto malformed;
git_buf_put(out, buf, eol - buf);
}
if (git_buf_oom(out))
goto oom;
return 0;
}
git_error_set(GIT_ERROR_OBJECT, "no such field '%s'", field);
return GIT_ENOTFOUND;
malformed:
git_error_set(GIT_ERROR_OBJECT, "malformed header");
return -1;
oom:
git_error_set_oom();
return -1;
}
int git_commit_extract_signature(git_buf *signature, git_buf *signed_data, git_repository *repo, git_oid *commit_id, const char *field)
{
git_odb_object *obj;
git_odb *odb;
const char *buf;
const char *h, *eol;
int error;
git_buf_clear(signature);
git_buf_clear(signed_data);
if (!field)
field = "gpgsig";
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
return error;
if ((error = git_odb_read(&obj, odb, commit_id)) < 0)
return error;
if (obj->cached.type != GIT_OBJECT_COMMIT) {
git_error_set(GIT_ERROR_INVALID, "the requested type does not match the type in ODB");
error = GIT_ENOTFOUND;
goto cleanup;
}
buf = git_odb_object_data(obj);
while ((h = strchr(buf, '\n')) && h[1] != '\0') {
h++;
if (git__prefixcmp(buf, field)) {
if (git_buf_put(signed_data, buf, h - buf) < 0)
return -1;
buf = h;
continue;
}
h = buf;
h += strlen(field);
eol = strchr(h, '\n');
if (h[0] != ' ') {
buf = h;
continue;
}
if (!eol)
goto malformed;
h++; /* skip the SP */
git_buf_put(signature, h, eol - h);
if (git_buf_oom(signature))
goto oom;
/* If the next line starts with SP, it's multi-line, we must continue */
while (eol[1] == ' ') {
git_buf_putc(signature, '\n');
h = eol + 2;
eol = strchr(h, '\n');
if (!eol)
goto malformed;
git_buf_put(signature, h, eol - h);
}
if (git_buf_oom(signature))
goto oom;
error = git_buf_puts(signed_data, eol+1);
git_odb_object_free(obj);
return error;
}
git_error_set(GIT_ERROR_OBJECT, "this commit is not signed");
error = GIT_ENOTFOUND;
goto cleanup;
malformed:
git_error_set(GIT_ERROR_OBJECT, "malformed header");
error = -1;
goto cleanup;
oom:
git_error_set_oom();
error = -1;
goto cleanup;
cleanup:
git_odb_object_free(obj);
git_buf_clear(signature);
git_buf_clear(signed_data);
return error;
}
int git_commit_create_buffer(git_buf *out,
git_repository *repo,
const git_signature *author,
const git_signature *committer,
const char *message_encoding,
const char *message,
const git_tree *tree,
size_t parent_count,
const git_commit *parents[])
{
int error;
commit_parent_data data = { parent_count, parents, repo };
git_array_oid_t parents_arr = GIT_ARRAY_INIT;
const git_oid *tree_id;
assert(tree && git_tree_owner(tree) == repo);
tree_id = git_tree_id(tree);
if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0)
return error;
error = git_commit__create_buffer_internal(
out, author, committer,
message_encoding, message, tree_id,
&parents_arr);
git_array_clear(parents_arr);
return error;
}
/**
* Append to 'out' properly marking continuations when there's a newline in 'content'
*/
static void format_header_field(git_buf *out, const char *field, const char *content)
{
const char *lf;
assert(out && field && content);
git_buf_puts(out, field);
git_buf_putc(out, ' ');
while ((lf = strchr(content, '\n')) != NULL) {
git_buf_put(out, content, lf - content);
git_buf_puts(out, "\n ");
content = lf + 1;
}
git_buf_puts(out, content);
git_buf_putc(out, '\n');
}
static const git_oid *commit_parent_from_commit(size_t n, void *payload)
{
const git_commit *commit = (const git_commit *) payload;
return git_array_get(commit->parent_ids, n);
}
int git_commit_create_with_signature(
git_oid *out,
git_repository *repo,
const char *commit_content,
const char *signature,
const char *signature_field)
{
git_odb *odb;
int error = 0;
const char *field;
const char *header_end;
git_buf commit = GIT_BUF_INIT;
git_commit *parsed;
git_array_oid_t parents = GIT_ARRAY_INIT;
/* The first step is to verify that all the tree and parents exist */
parsed = git__calloc(1, sizeof(git_commit));
GIT_ERROR_CHECK_ALLOC(parsed);
if ((error = commit_parse(parsed, commit_content, strlen(commit_content), 0)) < 0)
goto cleanup;
if ((error = validate_tree_and_parents(&parents, repo, &parsed->tree_id, commit_parent_from_commit, parsed, NULL, true)) < 0)
goto cleanup;
git_array_clear(parents);
/* Then we start appending by identifying the end of the commit header */
header_end = strstr(commit_content, "\n\n");
if (!header_end) {
git_error_set(GIT_ERROR_INVALID, "malformed commit contents");
error = -1;
goto cleanup;
}
/* The header ends after the first LF */
header_end++;
git_buf_put(&commit, commit_content, header_end - commit_content);
if (signature != NULL) {
field = signature_field ? signature_field : "gpgsig";
format_header_field(&commit, field, signature);
}
git_buf_puts(&commit, header_end);
if (git_buf_oom(&commit))
return -1;
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
goto cleanup;
if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJECT_COMMIT)) < 0)
goto cleanup;
cleanup:
git_commit__free(parsed);
git_buf_dispose(&commit);
return error;
}
int git_commit_committer_with_mailmap(
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
{
return git_mailmap_resolve_signature(out, mailmap, commit->committer);
}
int git_commit_author_with_mailmap(
git_signature **out, const git_commit *commit, const git_mailmap *mailmap)
{
return git_mailmap_resolve_signature(out, mailmap, commit->author);
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_commit_h__
#define INCLUDE_commit_h__
#include "common.h"
#include "git2/commit.h"
#include "tree.h"
#include "repository.h"
#include "array.h"
#include <time.h>
struct git_commit {
git_object object;
git_array_t(git_oid) parent_ids;
git_oid tree_id;
git_signature *author;
git_signature *committer;
char *message_encoding;
char *raw_message;
char *raw_header;
char *summary;
char *body;
};
void git_commit__free(void *commit);
int git_commit__parse(void *commit, git_odb_object *obj);
int git_commit__parse_raw(void *commit, const char *data, size_t size);
typedef enum {
GIT_COMMIT_PARSE_QUICK = (1 << 0), /**< Only parse parents and committer info */
} git_commit__parse_flags;
int git_commit__parse_ext(git_commit *commit, git_odb_object *odb_obj, unsigned int flags);
#endif
+163
View File
@@ -0,0 +1,163 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "commit_list.h"
#include "revwalk.h"
#include "pool.h"
#include "odb.h"
#include "commit.h"
int git_commit_list_time_cmp(const void *a, const void *b)
{
int64_t time_a = ((git_commit_list_node *) a)->time;
int64_t time_b = ((git_commit_list_node *) b)->time;
if (time_a < time_b)
return 1;
if (time_a > time_b)
return -1;
return 0;
}
git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p)
{
git_commit_list *new_list = git__malloc(sizeof(git_commit_list));
if (new_list != NULL) {
new_list->item = item;
new_list->next = *list_p;
}
*list_p = new_list;
return new_list;
}
git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p)
{
git_commit_list **pp = list_p;
git_commit_list *p;
while ((p = *pp) != NULL) {
if (git_commit_list_time_cmp(p->item, item) > 0)
break;
pp = &p->next;
}
return git_commit_list_insert(item, pp);
}
git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk)
{
return (git_commit_list_node *)git_pool_mallocz(&walk->commit_pool, 1);
}
static git_commit_list_node **alloc_parents(
git_revwalk *walk, git_commit_list_node *commit, size_t n_parents)
{
size_t bytes;
if (n_parents <= PARENTS_PER_COMMIT)
return (git_commit_list_node **)((char *)commit + sizeof(git_commit_list_node));
if (git__multiply_sizet_overflow(&bytes, n_parents, sizeof(git_commit_list_node *)))
return NULL;
return (git_commit_list_node **)git_pool_malloc(&walk->commit_pool, bytes);
}
void git_commit_list_free(git_commit_list **list_p)
{
git_commit_list *list = *list_p;
if (list == NULL)
return;
while (list) {
git_commit_list *temp = list;
list = temp->next;
git__free(temp);
}
*list_p = NULL;
}
git_commit_list_node *git_commit_list_pop(git_commit_list **stack)
{
git_commit_list *top = *stack;
git_commit_list_node *item = top ? top->item : NULL;
if (top) {
*stack = top->next;
git__free(top);
}
return item;
}
static int commit_quick_parse(
git_revwalk *walk,
git_commit_list_node *node,
git_odb_object *obj)
{
git_oid *parent_oid;
git_commit *commit;
int error;
size_t i;
commit = git__calloc(1, sizeof(*commit));
GIT_ERROR_CHECK_ALLOC(commit);
commit->object.repo = walk->repo;
if ((error = git_commit__parse_ext(commit, obj, GIT_COMMIT_PARSE_QUICK)) < 0) {
git__free(commit);
return error;
}
if (!git__is_uint16(git_array_size(commit->parent_ids))) {
git__free(commit);
git_error_set(GIT_ERROR_INVALID, "commit has more than 2^16 parents");
return -1;
}
node->time = commit->committer->when.time;
node->out_degree = (uint16_t) git_array_size(commit->parent_ids);
node->parents = alloc_parents(walk, node, node->out_degree);
GIT_ERROR_CHECK_ALLOC(node->parents);
git_array_foreach(commit->parent_ids, i, parent_oid) {
node->parents[i] = git_revwalk__commit_lookup(walk, parent_oid);
}
git_commit__free(commit);
node->parsed = 1;
return 0;
}
int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit)
{
git_odb_object *obj;
int error;
if (commit->parsed)
return 0;
if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0)
return error;
if (obj->cached.type != GIT_OBJECT_COMMIT) {
git_error_set(GIT_ERROR_INVALID, "object is no commit object");
error = -1;
} else
error = commit_quick_parse(walk, commit, obj);
git_odb_object_free(obj);
return error;
}
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_commit_list_h__
#define INCLUDE_commit_list_h__
#include "common.h"
#include "git2/oid.h"
#define PARENT1 (1 << 0)
#define PARENT2 (1 << 1)
#define RESULT (1 << 2)
#define STALE (1 << 3)
#define ALL_FLAGS (PARENT1 | PARENT2 | STALE | RESULT)
#define PARENTS_PER_COMMIT 2
#define COMMIT_ALLOC \
(sizeof(git_commit_list_node) + PARENTS_PER_COMMIT * sizeof(git_commit_list_node *))
#define FLAG_BITS 4
typedef struct git_commit_list_node {
git_oid oid;
int64_t time;
unsigned int seen:1,
uninteresting:1,
topo_delay:1,
parsed:1,
added:1,
flags : FLAG_BITS;
uint16_t in_degree;
uint16_t out_degree;
struct git_commit_list_node **parents;
} git_commit_list_node;
typedef struct git_commit_list {
git_commit_list_node *item;
struct git_commit_list *next;
} git_commit_list;
git_commit_list_node *git_commit_list_alloc_node(git_revwalk *walk);
int git_commit_list_time_cmp(const void *a, const void *b);
void git_commit_list_free(git_commit_list **list_p);
git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p);
git_commit_list *git_commit_list_insert_by_date(git_commit_list_node *item, git_commit_list **list_p);
int git_commit_list_parse(git_revwalk *walk, git_commit_list_node *commit);
git_commit_list_node *git_commit_list_pop(git_commit_list **stack);
#endif
+183
View File
@@ -0,0 +1,183 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_common_h__
#define INCLUDE_common_h__
#ifndef LIBGIT2_NO_FEATURES_H
# include "git2/sys/features.h"
#endif
#include "git2/common.h"
#include "cc-compat.h"
/** Declare a function as always inlined. */
#if defined(_MSC_VER)
# define GIT_INLINE(type) static __inline type
#elif defined(__GNUC__)
# define GIT_INLINE(type) static __inline__ type
#else
# define GIT_INLINE(type) static type
#endif
/** Support for gcc/clang __has_builtin intrinsic */
#ifndef __has_builtin
# define __has_builtin(x) 0
#endif
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef GIT_WIN32
# include <io.h>
# include <direct.h>
# include <winsock2.h>
# include <windows.h>
# include <ws2tcpip.h>
# include "win32/msvc-compat.h"
# include "win32/mingw-compat.h"
# include "win32/w32_common.h"
# include "win32/win32-compat.h"
# include "win32/error.h"
# include "win32/version.h"
# ifdef GIT_THREADS
# include "win32/thread.h"
# endif
#else
# include <unistd.h>
# include <strings.h>
# ifdef GIT_THREADS
# include <pthread.h>
# include <sched.h>
# endif
#define GIT_STDLIB_CALL
#ifdef GIT_USE_STAT_ATIMESPEC
# define st_atim st_atimespec
# define st_ctim st_ctimespec
# define st_mtim st_mtimespec
#endif
# include <arpa/inet.h>
#endif
#include "git2/types.h"
#include "git2/errors.h"
#include "errors.h"
#include "thread-utils.h"
#include "integer.h"
/*
* Include the declarations for deprecated functions; this ensures
* that they're decorated with the proper extern/visibility attributes.
*/
#include "git2/deprecated.h"
#include "posix.h"
#define DEFAULT_BUFSIZE 65536
#define FILEIO_BUFSIZE DEFAULT_BUFSIZE
#define FILTERIO_BUFSIZE DEFAULT_BUFSIZE
#define NETIO_BUFSIZE DEFAULT_BUFSIZE
/**
* Check a pointer allocation result, returning -1 if it failed.
*/
#define GIT_ERROR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; }
/**
* Check a buffer allocation result, returning -1 if it failed.
*/
#define GIT_ERROR_CHECK_ALLOC_BUF(buf) if ((void *)(buf) == NULL || git_buf_oom(buf)) { return -1; }
/**
* Check a return value and propagate result if non-zero.
*/
#define GIT_ERROR_CHECK_ERROR(code) \
do { int _err = (code); if (_err) return _err; } while (0)
/**
* Check a versioned structure for validity
*/
GIT_INLINE(int) git_error__check_version(const void *structure, unsigned int expected_max, const char *name)
{
unsigned int actual;
if (!structure)
return 0;
actual = *(const unsigned int*)structure;
if (actual > 0 && actual <= expected_max)
return 0;
git_error_set(GIT_ERROR_INVALID, "invalid version %d on %s", actual, name);
return -1;
}
#define GIT_ERROR_CHECK_VERSION(S,V,N) if (git_error__check_version(S,V,N) < 0) return -1
/**
* Initialize a structure with a version.
*/
GIT_INLINE(void) git__init_structure(void *structure, size_t len, unsigned int version)
{
memset(structure, 0, len);
*((int*)structure) = version;
}
#define GIT_INIT_STRUCTURE(S,V) git__init_structure(S, sizeof(*S), V)
#define GIT_INIT_STRUCTURE_FROM_TEMPLATE(PTR,VERSION,TYPE,TPL) do { \
TYPE _tmpl = TPL; \
GIT_ERROR_CHECK_VERSION(&(VERSION), _tmpl.version, #TYPE); \
memcpy((PTR), &_tmpl, sizeof(_tmpl)); } while (0)
/** Check for additive overflow, setting an error if would occur. */
#define GIT_ADD_SIZET_OVERFLOW(out, one, two) \
(git__add_sizet_overflow(out, one, two) ? (git_error_set_oom(), 1) : 0)
/** Check for additive overflow, setting an error if would occur. */
#define GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize) \
(git__multiply_sizet_overflow(out, nelem, elsize) ? (git_error_set_oom(), 1) : 0)
/** Check for additive overflow, failing if it would occur. */
#define GIT_ERROR_CHECK_ALLOC_ADD(out, one, two) \
if (GIT_ADD_SIZET_OVERFLOW(out, one, two)) { return -1; }
#define GIT_ERROR_CHECK_ALLOC_ADD3(out, one, two, three) \
if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), three)) { return -1; }
#define GIT_ERROR_CHECK_ALLOC_ADD4(out, one, two, three, four) \
if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), four)) { return -1; }
#define GIT_ERROR_CHECK_ALLOC_ADD5(out, one, two, three, four, five) \
if (GIT_ADD_SIZET_OVERFLOW(out, one, two) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), three) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), four) || \
GIT_ADD_SIZET_OVERFLOW(out, *(out), five)) { return -1; }
/** Check for multiplicative overflow, failing if it would occur. */
#define GIT_ERROR_CHECK_ALLOC_MULTIPLY(out, nelem, elsize) \
if (GIT_MULTIPLY_SIZET_OVERFLOW(out, nelem, elsize)) { return -1; }
/* NOTE: other git_error functions are in the public errors.h header file */
#include "util.h"
#endif
+1519
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_config_h__
#define INCLUDE_config_h__
#include "common.h"
#include "git2.h"
#include "git2/config.h"
#include "vector.h"
#include "repository.h"
#define GIT_CONFIG_FILENAME_PROGRAMDATA "config"
#define GIT_CONFIG_FILENAME_SYSTEM "gitconfig"
#define GIT_CONFIG_FILENAME_GLOBAL ".gitconfig"
#define GIT_CONFIG_FILENAME_XDG "config"
#define GIT_CONFIG_FILENAME_INREPO "config"
#define GIT_CONFIG_FILE_MODE 0666
struct git_config {
git_refcount rc;
git_vector backends;
};
extern int git_config__global_location(git_buf *buf);
extern int git_config_rename_section(
git_repository *repo,
const char *old_section_name, /* eg "branch.dummy" */
const char *new_section_name); /* NULL to drop the old section */
extern int git_config__normalize_name(const char *in, char **out);
/* internal only: does not normalize key and sets out to NULL if not found */
extern int git_config__lookup_entry(
git_config_entry **out,
const git_config *cfg,
const char *key,
bool no_errors);
/* internal only: update and/or delete entry string with constraints */
extern int git_config__update_entry(
git_config *cfg,
const char *key,
const char *value,
bool overwrite_existing,
bool only_if_existing);
/*
* Lookup functions that cannot fail. These functions look up a config
* value and return a fallback value if the value is missing or if any
* failures occur while trying to access the value.
*/
extern char *git_config__get_string_force(
const git_config *cfg, const char *key, const char *fallback_value);
extern int git_config__get_bool_force(
const git_config *cfg, const char *key, int fallback_value);
extern int git_config__get_int_force(
const git_config *cfg, const char *key, int fallback_value);
/* API for repository configmap-style lookups from config - not cached, but
* uses configmap value maps and fallbacks
*/
extern int git_config__configmap_lookup(
int *out, git_config *config, git_configmap_item item);
/**
* The opposite of git_config_lookup_map_value, we take an enum value
* and map it to the string or bool value on the config.
*/
int git_config_lookup_map_enum(git_configmap_t *type_out,
const char **str_out, const git_configmap *maps,
size_t map_n, int enum_val);
/**
* Unlock the backend with the highest priority
*
* Unlocking will allow other writers to updat the configuration
* file. Optionally, any changes performed since the lock will be
* applied to the configuration.
*
* @param cfg the configuration
* @param commit boolean which indicates whether to commit any changes
* done since locking
* @return 0 or an error code
*/
GIT_EXTERN(int) git_config_unlock(git_config *cfg, int commit);
#endif
+96
View File
@@ -0,0 +1,96 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_config_file_h__
#define INCLUDE_config_file_h__
#include "common.h"
#include "git2/sys/config.h"
#include "git2/config.h"
/**
* Create a configuration file backend for ondisk files
*
* These are the normal `.gitconfig` files that Core Git
* 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
* @param path where the config file is located
*/
extern int git_config_backend_from_file(git_config_backend **out, const char *path);
/**
* Create a readonly configuration file backend from another backend
*
* This copies the complete contents of the source backend to the
* new backend. The new backend will be completely read-only and
* cannot be modified.
*
* @param out the new snapshotted backend
* @param source the backend to copy
*/
extern int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source);
/**
* Create an in-memory configuration file backend
*
* @param out the new backend
* @param cfg the configuration that is to be parsed
* @param len the length of the string pointed to by `cfg`
*/
extern int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len);
GIT_INLINE(int) git_config_backend_open(git_config_backend *cfg, unsigned int level, const git_repository *repo)
{
return cfg->open(cfg, level, repo);
}
GIT_INLINE(void) git_config_backend_free(git_config_backend *cfg)
{
if (cfg)
cfg->free(cfg);
}
GIT_INLINE(int) git_config_backend_get_string(
git_config_entry **out, git_config_backend *cfg, const char *name)
{
return cfg->get(cfg, name, out);
}
GIT_INLINE(int) git_config_backend_set_string(
git_config_backend *cfg, const char *name, const char *value)
{
return cfg->set(cfg, name, value);
}
GIT_INLINE(int) git_config_backend_delete(
git_config_backend *cfg, const char *name)
{
return cfg->del(cfg, name);
}
GIT_INLINE(int) git_config_backend_foreach(
git_config_backend *cfg,
int (*fn)(const git_config_entry *entry, void *data),
void *data)
{
return git_config_backend_foreach_match(cfg, NULL, fn, data);
}
GIT_INLINE(int) git_config_backend_lock(git_config_backend *cfg)
{
return cfg->lock(cfg);
}
GIT_INLINE(int) git_config_backend_unlock(git_config_backend *cfg, int success)
{
return cfg->unlock(cfg, success);
}
#endif
+137
View File
@@ -0,0 +1,137 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "futils.h"
#include "repository.h"
#include "config.h"
#include "git2/config.h"
#include "vector.h"
#include "filter.h"
struct map_data {
const char *name;
git_configmap *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_configmap _configmap_eol[] = {
{GIT_CONFIGMAP_FALSE, NULL, GIT_EOL_UNSET},
{GIT_CONFIGMAP_STRING, "lf", GIT_EOL_LF},
{GIT_CONFIGMAP_STRING, "crlf", GIT_EOL_CRLF},
{GIT_CONFIGMAP_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_configmap _configmap_autocrlf[] = {
{GIT_CONFIGMAP_FALSE, NULL, GIT_AUTO_CRLF_FALSE},
{GIT_CONFIGMAP_TRUE, NULL, GIT_AUTO_CRLF_TRUE},
{GIT_CONFIGMAP_STRING, "input", GIT_AUTO_CRLF_INPUT}
};
static git_configmap _configmap_safecrlf[] = {
{GIT_CONFIGMAP_FALSE, NULL, GIT_SAFE_CRLF_FALSE},
{GIT_CONFIGMAP_TRUE, NULL, GIT_SAFE_CRLF_FAIL},
{GIT_CONFIGMAP_STRING, "warn", GIT_SAFE_CRLF_WARN}
};
static git_configmap _configmap_logallrefupdates[] = {
{GIT_CONFIGMAP_FALSE, NULL, GIT_LOGALLREFUPDATES_FALSE},
{GIT_CONFIGMAP_TRUE, NULL, GIT_LOGALLREFUPDATES_TRUE},
{GIT_CONFIGMAP_STRING, "always", GIT_LOGALLREFUPDATES_ALWAYS},
};
/*
* Generic map for integer values
*/
static git_configmap _configmap_int[] = {
{GIT_CONFIGMAP_INT32, NULL, 0},
};
static struct map_data _configmaps[] = {
{"core.autocrlf", _configmap_autocrlf, ARRAY_SIZE(_configmap_autocrlf), GIT_AUTO_CRLF_DEFAULT},
{"core.eol", _configmap_eol, ARRAY_SIZE(_configmap_eol), GIT_EOL_DEFAULT},
{"core.symlinks", NULL, 0, GIT_SYMLINKS_DEFAULT },
{"core.ignorecase", NULL, 0, GIT_IGNORECASE_DEFAULT },
{"core.filemode", NULL, 0, GIT_FILEMODE_DEFAULT },
{"core.ignorestat", NULL, 0, GIT_IGNORESTAT_DEFAULT },
{"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT },
{"core.abbrev", _configmap_int, 1, GIT_ABBREV_DEFAULT },
{"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT },
{"core.safecrlf", _configmap_safecrlf, ARRAY_SIZE(_configmap_safecrlf), GIT_SAFE_CRLF_DEFAULT},
{"core.logallrefupdates", _configmap_logallrefupdates, ARRAY_SIZE(_configmap_logallrefupdates), GIT_LOGALLREFUPDATES_DEFAULT},
{"core.protecthfs", NULL, 0, GIT_PROTECTHFS_DEFAULT },
{"core.protectntfs", NULL, 0, GIT_PROTECTNTFS_DEFAULT },
{"core.fsyncobjectfiles", NULL, 0, GIT_FSYNCOBJECTFILES_DEFAULT },
};
int git_config__configmap_lookup(int *out, git_config *config, git_configmap_item item)
{
int error = 0;
struct map_data *data = &_configmaps[(int)item];
git_config_entry *entry;
if ((error = git_config__lookup_entry(&entry, config, data->name, false)) < 0)
return error;
if (!entry)
*out = data->default_value;
else if (data->maps)
error = git_config_lookup_map_value(
out, data->maps, data->map_count, entry->value);
else
error = git_config_parse_bool(out, entry->value);
git_config_entry_free(entry);
return error;
}
int git_repository__configmap_lookup(int *out, git_repository *repo, git_configmap_item item)
{
*out = repo->configmap_cache[(int)item];
if (*out == GIT_CONFIGMAP_NOT_CACHED) {
int error;
git_config *config;
if ((error = git_repository_config__weakptr(&config, repo)) < 0 ||
(error = git_config__configmap_lookup(out, config, item)) < 0)
return error;
repo->configmap_cache[(int)item] = *out;
}
return 0;
}
void git_repository__configmap_lookup_cache_clear(git_repository *repo)
{
int i;
for (i = 0; i < GIT_CONFIGMAP_CACHE_MAX; ++i)
repo->configmap_cache[i] = GIT_CONFIGMAP_NOT_CACHED;
}
+229
View File
@@ -0,0 +1,229 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "config_entries.h"
typedef struct config_entry_list {
struct config_entry_list *next;
struct config_entry_list *last;
git_config_entry *entry;
bool first;
} config_entry_list;
typedef struct config_entries_iterator {
git_config_iterator parent;
git_config_entries *entries;
config_entry_list *head;
} config_entries_iterator;
struct git_config_entries {
git_refcount rc;
git_strmap *map;
config_entry_list *list;
};
int git_config_entries_new(git_config_entries **out)
{
git_config_entries *entries;
int error;
entries = git__calloc(1, sizeof(git_config_entries));
GIT_ERROR_CHECK_ALLOC(entries);
GIT_REFCOUNT_INC(entries);
if ((error = git_strmap_new(&entries->map)) < 0)
git__free(entries);
else
*out = entries;
return error;
}
int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry)
{
git_config_entry *duplicated;
int error;
duplicated = git__calloc(1, sizeof(git_config_entry));
GIT_ERROR_CHECK_ALLOC(duplicated);
duplicated->name = git__strdup(entry->name);
GIT_ERROR_CHECK_ALLOC(duplicated->name);
if (entry->value) {
duplicated->value = git__strdup(entry->value);
GIT_ERROR_CHECK_ALLOC(duplicated->value);
}
duplicated->level = entry->level;
duplicated->include_depth = entry->include_depth;
if ((error = git_config_entries_append(entries, duplicated)) < 0)
goto out;
out:
if (error && duplicated) {
git__free((char *) duplicated->name);
git__free((char *) duplicated->value);
git__free(duplicated);
}
return error;
}
int git_config_entries_dup(git_config_entries **out, git_config_entries *entries)
{
git_config_entries *result = NULL;
config_entry_list *head;
int error;
if ((error = git_config_entries_new(&result)) < 0)
goto out;
for (head = entries->list; head; head = head->next)
if ((git_config_entries_dup_entry(result, head->entry)) < 0)
goto out;
*out = result;
result = NULL;
out:
git_config_entries_free(result);
return error;
}
void git_config_entries_incref(git_config_entries *entries)
{
GIT_REFCOUNT_INC(entries);
}
static void config_entries_free(git_config_entries *entries)
{
config_entry_list *list = NULL, *next;
git_strmap_free(entries->map);
list = entries->list;
while (list != NULL) {
next = list->next;
if (list->first)
git__free((char *) list->entry->name);
git__free((char *) list->entry->value);
git__free(list->entry);
git__free(list);
list = next;
}
git__free(entries);
}
void git_config_entries_free(git_config_entries *entries)
{
if (entries)
GIT_REFCOUNT_DEC(entries, config_entries_free);
}
int git_config_entries_append(git_config_entries *entries, git_config_entry *entry)
{
config_entry_list *existing, *head;
head = git__calloc(1, sizeof(config_entry_list));
GIT_ERROR_CHECK_ALLOC(head);
head->entry = entry;
/*
* This is a micro-optimization for configuration files
* with a lot of same keys. As for multivars the entry's
* key will be the same for all entries, we can just free
* all except the first entry's name and just re-use it.
*/
if ((existing = git_strmap_get(entries->map, entry->name)) != NULL) {
git__free((char *) entry->name);
entry->name = existing->entry->name;
} else {
head->first = 1;
}
if (entries->list)
entries->list->last->next = head;
else
entries->list = head;
entries->list->last = head;
if (git_strmap_set(entries->map, entry->name, head) < 0)
return -1;
return 0;
}
int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key)
{
config_entry_list *entry;
if ((entry = git_strmap_get(entries->map, key)) == NULL)
return GIT_ENOTFOUND;
*out = entry->entry;
return 0;
}
int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key)
{
config_entry_list *entry;
if ((entry = git_strmap_get(entries->map, key)) == NULL)
return GIT_ENOTFOUND;
if (!entry->first) {
git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being a multivar");
return -1;
}
if (entry->entry->include_depth) {
git_error_set(GIT_ERROR_CONFIG, "entry is not unique due to being included");
return -1;
}
*out = entry->entry;
return 0;
}
static void config_iterator_free(git_config_iterator *iter)
{
config_entries_iterator *it = (config_entries_iterator *) iter;
git_config_entries_free(it->entries);
git__free(it);
}
static int config_iterator_next(
git_config_entry **entry,
git_config_iterator *iter)
{
config_entries_iterator *it = (config_entries_iterator *) iter;
if (!it->head)
return GIT_ITEROVER;
*entry = it->head->entry;
it->head = it->head->next;
return 0;
}
int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries)
{
config_entries_iterator *it;
it = git__calloc(1, sizeof(config_entries_iterator));
GIT_ERROR_CHECK_ALLOC(it);
it->parent.next = config_iterator_next;
it->parent.free = config_iterator_free;
it->head = entries->list;
it->entries = entries;
git_config_entries_incref(entries);
*out = &it->parent;
return 0;
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "git2/sys/config.h"
#include "config.h"
typedef struct git_config_entries git_config_entries;
int git_config_entries_new(git_config_entries **out);
int git_config_entries_dup(git_config_entries **out, git_config_entries *entries);
int git_config_entries_dup_entry(git_config_entries *entries, const git_config_entry *entry);
void git_config_entries_incref(git_config_entries *entries);
void git_config_entries_free(git_config_entries *entries);
/* Add or append the new config option */
int git_config_entries_append(git_config_entries *entries, git_config_entry *entry);
int git_config_entries_get(git_config_entry **out, git_config_entries *entries, const char *key);
int git_config_entries_get_unique(git_config_entry **out, git_config_entries *entries, const char *key);
int git_config_entries_iterator_new(git_config_iterator **out, git_config_entries *entries);
File diff suppressed because it is too large Load Diff
+220
View File
@@ -0,0 +1,220 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "config.h"
#include "config_backend.h"
#include "config_parse.h"
#include "config_entries.h"
typedef struct {
git_config_backend parent;
git_config_entries *entries;
git_buf cfg;
} config_memory_backend;
typedef struct {
git_config_entries *entries;
git_config_level_t level;
} config_memory_parse_data;
static int config_error_readonly(void)
{
git_error_set(GIT_ERROR_CONFIG, "this backend is read-only");
return -1;
}
static int read_variable_cb(
git_config_parser *reader,
const char *current_section,
const char *var_name,
const char *var_value,
const char *line,
size_t line_len,
void *payload)
{
config_memory_parse_data *parse_data = (config_memory_parse_data *) payload;
git_buf buf = GIT_BUF_INIT;
git_config_entry *entry;
const char *c;
int result;
GIT_UNUSED(reader);
GIT_UNUSED(line);
GIT_UNUSED(line_len);
if (current_section) {
/* TODO: Once warnings land, we should likely warn
* here. Git appears to warn in most cases if it sees
* un-namespaced config options.
*/
git_buf_puts(&buf, current_section);
git_buf_putc(&buf, '.');
}
for (c = var_name; *c; c++)
git_buf_putc(&buf, git__tolower(*c));
if (git_buf_oom(&buf))
return -1;
entry = git__calloc(1, sizeof(git_config_entry));
GIT_ERROR_CHECK_ALLOC(entry);
entry->name = git_buf_detach(&buf);
entry->value = var_value ? git__strdup(var_value) : NULL;
entry->level = parse_data->level;
entry->include_depth = 0;
if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
return result;
return result;
}
static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo)
{
config_memory_backend *memory_backend = (config_memory_backend *) backend;
git_config_parser parser = GIT_PARSE_CTX_INIT;
config_memory_parse_data parse_data;
int error;
GIT_UNUSED(repo);
if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr,
memory_backend->cfg.size)) < 0)
goto out;
parse_data.entries = memory_backend->entries;
parse_data.level = level;
if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0)
goto out;
out:
git_config_parser_dispose(&parser);
return error;
}
static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out)
{
config_memory_backend *memory_backend = (config_memory_backend *) backend;
return git_config_entries_get(out, memory_backend->entries, key);
}
static int config_memory_iterator(
git_config_iterator **iter,
git_config_backend *backend)
{
config_memory_backend *memory_backend = (config_memory_backend *) backend;
git_config_entries *entries;
int error;
if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0)
goto out;
if ((error = git_config_entries_iterator_new(iter, entries)) < 0)
goto out;
out:
/* Let iterator delete duplicated entries when it's done */
git_config_entries_free(entries);
return error;
}
static int config_memory_set(git_config_backend *backend, const char *name, const char *value)
{
GIT_UNUSED(backend);
GIT_UNUSED(name);
GIT_UNUSED(value);
return config_error_readonly();
}
static int config_memory_set_multivar(
git_config_backend *backend, const char *name, const char *regexp, const char *value)
{
GIT_UNUSED(backend);
GIT_UNUSED(name);
GIT_UNUSED(regexp);
GIT_UNUSED(value);
return config_error_readonly();
}
static int config_memory_delete(git_config_backend *backend, const char *name)
{
GIT_UNUSED(backend);
GIT_UNUSED(name);
return config_error_readonly();
}
static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp)
{
GIT_UNUSED(backend);
GIT_UNUSED(name);
GIT_UNUSED(regexp);
return config_error_readonly();
}
static int config_memory_lock(git_config_backend *backend)
{
GIT_UNUSED(backend);
return config_error_readonly();
}
static int config_memory_unlock(git_config_backend *backend, int success)
{
GIT_UNUSED(backend);
GIT_UNUSED(success);
return config_error_readonly();
}
static void config_memory_free(git_config_backend *_backend)
{
config_memory_backend *backend = (config_memory_backend *)_backend;
if (backend == NULL)
return;
git_config_entries_free(backend->entries);
git_buf_dispose(&backend->cfg);
git__free(backend);
}
int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len)
{
config_memory_backend *backend;
backend = git__calloc(1, sizeof(config_memory_backend));
GIT_ERROR_CHECK_ALLOC(backend);
if (git_config_entries_new(&backend->entries) < 0) {
git__free(backend);
return -1;
}
if (git_buf_set(&backend->cfg, cfg, len) < 0) {
git_config_entries_free(backend->entries);
git__free(backend);
return -1;
}
backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
backend->parent.readonly = 1;
backend->parent.open = config_memory_open;
backend->parent.get = config_memory_get;
backend->parent.set = config_memory_set;
backend->parent.set_multivar = config_memory_set_multivar;
backend->parent.del = config_memory_delete;
backend->parent.del_multivar = config_memory_delete_multivar;
backend->parent.iterator = config_memory_iterator;
backend->parent.lock = config_memory_lock;
backend->parent.unlock = config_memory_unlock;
backend->parent.snapshot = git_config_backend_snapshot;
backend->parent.free = config_memory_free;
*out = (git_config_backend *)backend;
return 0;
}
+578
View File
@@ -0,0 +1,578 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "config_parse.h"
#include "buf_text.h"
#include <ctype.h>
const char *git_config_escapes = "ntb\"\\";
const char *git_config_escaped = "\n\t\b\"\\";
static void set_parse_error(git_config_parser *reader, int col, const char *error_str)
{
if (col)
git_error_set(GIT_ERROR_CONFIG,
"failed to parse config file: %s (in %s:%"PRIuZ", column %d)",
error_str, reader->path, reader->ctx.line_num, col);
else
git_error_set(GIT_ERROR_CONFIG,
"failed to parse config file: %s (in %s:%"PRIuZ")",
error_str, reader->path, reader->ctx.line_num);
}
GIT_INLINE(int) config_keychar(int c)
{
return isalnum(c) || c == '-';
}
static int strip_comments(char *line, int in_quotes)
{
int quote_count = in_quotes, backslash_count = 0;
char *ptr;
for (ptr = line; *ptr; ++ptr) {
if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
quote_count++;
if ((ptr[0] == ';' || ptr[0] == '#') &&
(quote_count % 2) == 0 &&
(backslash_count % 2) == 0) {
ptr[0] = '\0';
break;
}
if (ptr[0] == '\\')
backslash_count++;
else
backslash_count = 0;
}
/* skip any space at the end */
while (ptr > line && git__isspace(ptr[-1])) {
ptr--;
}
ptr[0] = '\0';
return quote_count;
}
static int parse_subsection_header(git_config_parser *reader, const char *line, size_t pos, const char *base_name, char **section_name)
{
int c, rpos;
const char *first_quote, *last_quote;
const char *line_start = line;
git_buf buf = GIT_BUF_INIT;
size_t quoted_len, alloc_len, base_name_len = strlen(base_name);
/* Skip any additional whitespace before our section name */
while (git__isspace(line[pos]))
pos++;
/* We should be at the first quotation mark. */
if (line[pos] != '"') {
set_parse_error(reader, 0, "missing quotation marks in section header");
goto end_error;
}
first_quote = &line[pos];
last_quote = strrchr(line, '"');
quoted_len = last_quote - first_quote;
if ((last_quote - line) > INT_MAX) {
set_parse_error(reader, 0, "invalid section header, line too long");
goto end_error;
}
if (quoted_len == 0) {
set_parse_error(reader, 0, "missing closing quotation mark in section header");
goto end_error;
}
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, base_name_len, quoted_len);
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, alloc_len, 2);
if (git_buf_grow(&buf, alloc_len) < 0 ||
git_buf_printf(&buf, "%s.", base_name) < 0)
goto end_error;
rpos = 0;
line = first_quote;
c = line[++rpos];
/*
* At the end of each iteration, whatever is stored in c will be
* added to the string. In case of error, jump to out
*/
do {
switch (c) {
case 0:
set_parse_error(reader, 0, "unexpected end-of-line in section header");
goto end_error;
case '"':
goto end_parse;
case '\\':
c = line[++rpos];
if (c == 0) {
set_parse_error(reader, rpos, "unexpected end-of-line in section header");
goto end_error;
}
default:
break;
}
git_buf_putc(&buf, (char)c);
c = line[++rpos];
} while (line + rpos < last_quote);
end_parse:
if (git_buf_oom(&buf))
goto end_error;
if (line[rpos] != '"' || line[rpos + 1] != ']') {
set_parse_error(reader, rpos, "unexpected text after closing quotes");
git_buf_dispose(&buf);
return -1;
}
*section_name = git_buf_detach(&buf);
return (int)(&line[rpos + 2] - line_start); /* rpos is at the closing quote */
end_error:
git_buf_dispose(&buf);
return -1;
}
static int parse_section_header(git_config_parser *reader, char **section_out)
{
char *name, *name_end;
int name_length, c, pos;
int result;
char *line;
size_t line_len;
git_parse_advance_ws(&reader->ctx);
line = git__strndup(reader->ctx.line, reader->ctx.line_len);
if (line == NULL)
return -1;
/* find the end of the variable's name */
name_end = strrchr(line, ']');
if (name_end == NULL) {
git__free(line);
set_parse_error(reader, 0, "missing ']' in section header");
return -1;
}
GIT_ERROR_CHECK_ALLOC_ADD(&line_len, (size_t)(name_end - line), 1);
name = git__malloc(line_len);
GIT_ERROR_CHECK_ALLOC(name);
name_length = 0;
pos = 0;
/* Make sure we were given a section header */
c = line[pos++];
assert(c == '[');
c = line[pos++];
do {
if (git__isspace(c)){
name[name_length] = '\0';
result = parse_subsection_header(reader, line, pos, name, section_out);
git__free(line);
git__free(name);
return result;
}
if (!config_keychar(c) && c != '.') {
set_parse_error(reader, pos, "unexpected character in header");
goto fail_parse;
}
name[name_length++] = (char)git__tolower(c);
} while ((c = line[pos++]) != ']');
if (line[pos - 1] != ']') {
set_parse_error(reader, pos, "unexpected end of file");
goto fail_parse;
}
git__free(line);
name[name_length] = 0;
*section_out = name;
return pos;
fail_parse:
git__free(line);
git__free(name);
return -1;
}
static int skip_bom(git_parse_ctx *parser)
{
git_buf buf = GIT_BUF_INIT_CONST(parser->content, parser->content_len);
git_bom_t bom;
int bom_offset = git_buf_text_detect_bom(&bom, &buf);
if (bom == GIT_BOM_UTF8)
git_parse_advance_chars(parser, bom_offset);
/* TODO: reference implementation is pretty stupid with BoM */
return 0;
}
/*
(* basic types *)
digit = "0".."9"
integer = digit { digit }
alphabet = "a".."z" + "A" .. "Z"
section_char = alphabet | "." | "-"
extension_char = (* any character except newline *)
any_char = (* any character *)
variable_char = "alphabet" | "-"
(* actual grammar *)
config = { section }
section = header { definition }
header = "[" section [subsection | subsection_ext] "]"
subsection = "." section
subsection_ext = "\"" extension "\""
section = section_char { section_char }
extension = extension_char { extension_char }
definition = variable_name ["=" variable_value] "\n"
variable_name = variable_char { variable_char }
variable_value = string | boolean | integer
string = quoted_string | plain_string
quoted_string = "\"" plain_string "\""
plain_string = { any_char }
boolean = boolean_true | boolean_false
boolean_true = "yes" | "1" | "true" | "on"
boolean_false = "no" | "0" | "false" | "off"
*/
/* '\"' -> '"' etc */
static int unescape_line(
char **out, bool *is_multi, const char *ptr, int quote_count)
{
char *str, *fixed, *esc;
size_t ptr_len = strlen(ptr), alloc_len;
*is_multi = false;
if (GIT_ADD_SIZET_OVERFLOW(&alloc_len, ptr_len, 1) ||
(str = git__malloc(alloc_len)) == NULL) {
return -1;
}
fixed = str;
while (*ptr != '\0') {
if (*ptr == '"') {
quote_count++;
} else if (*ptr != '\\') {
*fixed++ = *ptr;
} else {
/* backslash, check the next char */
ptr++;
/* if we're at the end, it's a multiline, so keep the backslash */
if (*ptr == '\0') {
*is_multi = true;
goto done;
}
if ((esc = strchr(git_config_escapes, *ptr)) != NULL) {
*fixed++ = git_config_escaped[esc - git_config_escapes];
} else {
git__free(str);
git_error_set(GIT_ERROR_CONFIG, "invalid escape at %s", ptr);
return -1;
}
}
ptr++;
}
done:
*fixed = '\0';
*out = str;
return 0;
}
static int parse_multiline_variable(git_config_parser *reader, git_buf *value, int in_quotes)
{
int quote_count;
bool multiline = true;
while (multiline) {
char *line = NULL, *proc_line = NULL;
int error;
/* Check that the next line exists */
git_parse_advance_line(&reader->ctx);
line = git__strndup(reader->ctx.line, reader->ctx.line_len);
GIT_ERROR_CHECK_ALLOC(line);
/*
* We've reached the end of the file, there is no continuation.
* (this is not an error).
*/
if (line[0] == '\0') {
error = 0;
goto out;
}
/* If it was just a comment, pretend it didn't exist */
quote_count = strip_comments(line, !!in_quotes);
if (line[0] == '\0')
goto next;
if ((error = unescape_line(&proc_line, &multiline,
line, in_quotes)) < 0)
goto out;
/* Add this line to the multiline var */
if ((error = git_buf_puts(value, proc_line)) < 0)
goto out;
next:
git__free(line);
git__free(proc_line);
in_quotes = quote_count;
continue;
out:
git__free(line);
git__free(proc_line);
return error;
}
return 0;
}
GIT_INLINE(bool) is_namechar(char c)
{
return isalnum(c) || c == '-';
}
static int parse_name(
char **name, const char **value, git_config_parser *reader, const char *line)
{
const char *name_end = line, *value_start;
*name = NULL;
*value = NULL;
while (*name_end && is_namechar(*name_end))
name_end++;
if (line == name_end) {
set_parse_error(reader, 0, "invalid configuration key");
return -1;
}
value_start = name_end;
while (*value_start && git__isspace(*value_start))
value_start++;
if (*value_start == '=') {
*value = value_start + 1;
} else if (*value_start) {
set_parse_error(reader, 0, "invalid configuration key");
return -1;
}
if ((*name = git__strndup(line, name_end - line)) == NULL)
return -1;
return 0;
}
static int parse_variable(git_config_parser *reader, char **var_name, char **var_value)
{
const char *value_start = NULL;
char *line = NULL, *name = NULL, *value = NULL;
int quote_count, error;
bool multiline;
*var_name = NULL;
*var_value = NULL;
git_parse_advance_ws(&reader->ctx);
line = git__strndup(reader->ctx.line, reader->ctx.line_len);
GIT_ERROR_CHECK_ALLOC(line);
quote_count = strip_comments(line, 0);
if ((error = parse_name(&name, &value_start, reader, line)) < 0)
goto out;
/*
* Now, let's try to parse the value
*/
if (value_start != NULL) {
while (git__isspace(value_start[0]))
value_start++;
if ((error = unescape_line(&value, &multiline, value_start, 0)) < 0)
goto out;
if (multiline) {
git_buf multi_value = GIT_BUF_INIT;
git_buf_attach(&multi_value, value, 0);
value = NULL;
if (parse_multiline_variable(reader, &multi_value, quote_count) < 0 ||
git_buf_oom(&multi_value)) {
error = -1;
git_buf_dispose(&multi_value);
goto out;
}
value = git_buf_detach(&multi_value);
}
}
*var_name = name;
*var_value = value;
name = NULL;
value = NULL;
out:
git__free(name);
git__free(value);
git__free(line);
return error;
}
int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen)
{
out->path = path;
return git_parse_ctx_init(&out->ctx, data, datalen);
}
void git_config_parser_dispose(git_config_parser *parser)
{
git_parse_ctx_clear(&parser->ctx);
}
int git_config_parse(
git_config_parser *parser,
git_config_parser_section_cb on_section,
git_config_parser_variable_cb on_variable,
git_config_parser_comment_cb on_comment,
git_config_parser_eof_cb on_eof,
void *payload)
{
git_parse_ctx *ctx;
char *current_section = NULL, *var_name = NULL, *var_value = NULL;
int result = 0;
ctx = &parser->ctx;
skip_bom(ctx);
for (; ctx->remain_len > 0; git_parse_advance_line(ctx)) {
const char *line_start;
size_t line_len;
char c;
restart:
line_start = ctx->line;
line_len = ctx->line_len;
/*
* Get either first non-whitespace character or, if that does
* not exist, the first whitespace character. This is required
* to preserve whitespaces when writing back the file.
*/
if (git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE) < 0 &&
git_parse_peek(&c, ctx, 0) < 0)
continue;
switch (c) {
case '[': /* section header, new section begins */
git__free(current_section);
current_section = NULL;
result = parse_section_header(parser, &current_section);
if (result < 0)
break;
git_parse_advance_chars(ctx, result);
if (on_section)
result = on_section(parser, current_section, line_start, line_len, payload);
/*
* After we've parsed the section header we may not be
* done with the line. If there's still data in there,
* run the next loop with the rest of the current line
* instead of moving forward.
*/
if (!git_parse_peek(&c, ctx, GIT_PARSE_PEEK_SKIP_WHITESPACE))
goto restart;
break;
case '\n': /* comment or whitespace-only */
case '\r':
case ' ':
case '\t':
case ';':
case '#':
if (on_comment) {
result = on_comment(parser, line_start, line_len, payload);
}
break;
default: /* assume variable declaration */
if ((result = parse_variable(parser, &var_name, &var_value)) == 0 && on_variable) {
result = on_variable(parser, current_section, var_name, var_value, line_start, line_len, payload);
git__free(var_name);
git__free(var_value);
}
break;
}
if (result < 0)
goto out;
}
if (on_eof)
result = on_eof(parser, current_section, payload);
out:
git__free(current_section);
return result;
}
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_config_parse_h__
#define INCLUDE_config_parse_h__
#include "common.h"
#include "array.h"
#include "futils.h"
#include "oid.h"
#include "parse.h"
extern const char *git_config_escapes;
extern const char *git_config_escaped;
typedef struct {
const char *path;
git_parse_ctx ctx;
} git_config_parser;
#define GIT_CONFIG_PARSER_INIT { NULL, GIT_PARSE_CTX_INIT }
typedef int (*git_config_parser_section_cb)(
git_config_parser *parser,
const char *current_section,
const char *line,
size_t line_len,
void *payload);
typedef int (*git_config_parser_variable_cb)(
git_config_parser *parser,
const char *current_section,
const char *var_name,
const char *var_value,
const char *line,
size_t line_len,
void *payload);
typedef int (*git_config_parser_comment_cb)(
git_config_parser *parser,
const char *line,
size_t line_len,
void *payload);
typedef int (*git_config_parser_eof_cb)(
git_config_parser *parser,
const char *current_section,
void *payload);
int git_config_parser_init(git_config_parser *out, const char *path, const char *data, size_t datalen);
void git_config_parser_dispose(git_config_parser *parser);
int git_config_parse(
git_config_parser *parser,
git_config_parser_section_cb on_section,
git_config_parser_variable_cb on_variable,
git_config_parser_comment_cb on_comment,
git_config_parser_eof_cb on_eof,
void *payload);
#endif
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "config.h"
#include "config_entries.h"
typedef struct {
git_config_backend parent;
git_mutex values_mutex;
git_config_entries *entries;
git_config_backend *source;
} config_snapshot_backend;
static int config_error_readonly(void)
{
git_error_set(GIT_ERROR_CONFIG, "this backend is read-only");
return -1;
}
static int config_snapshot_iterator(
git_config_iterator **iter,
struct git_config_backend *backend)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(backend, config_snapshot_backend, parent);
git_config_entries *entries = NULL;
int error;
if ((error = git_config_entries_dup(&entries, b->entries)) < 0 ||
(error = git_config_entries_iterator_new(iter, entries)) < 0)
goto out;
out:
/* Let iterator delete duplicated entries when it's done */
git_config_entries_free(entries);
return error;
}
/* release the map containing the entry as an equivalent to freeing it */
static void config_snapshot_entry_free(git_config_entry *entry)
{
git_config_entries *entries = (git_config_entries *) entry->payload;
git_config_entries_free(entries);
}
static int config_snapshot_get(git_config_backend *cfg, const char *key, git_config_entry **out)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent);
git_config_entries *entries = NULL;
git_config_entry *entry;
int error = 0;
if (git_mutex_lock(&b->values_mutex) < 0) {
git_error_set(GIT_ERROR_OS, "failed to lock config backend");
return -1;
}
entries = b->entries;
git_config_entries_incref(entries);
git_mutex_unlock(&b->values_mutex);
if ((error = (git_config_entries_get(&entry, entries, key))) < 0) {
git_config_entries_free(entries);
return error;
}
entry->free = config_snapshot_entry_free;
entry->payload = entries;
*out = entry;
return 0;
}
static int config_snapshot_set(git_config_backend *cfg, const char *name, const char *value)
{
GIT_UNUSED(cfg);
GIT_UNUSED(name);
GIT_UNUSED(value);
return config_error_readonly();
}
static int config_snapshot_set_multivar(
git_config_backend *cfg, const char *name, const char *regexp, const char *value)
{
GIT_UNUSED(cfg);
GIT_UNUSED(name);
GIT_UNUSED(regexp);
GIT_UNUSED(value);
return config_error_readonly();
}
static int config_snapshot_delete_multivar(git_config_backend *cfg, const char *name, const char *regexp)
{
GIT_UNUSED(cfg);
GIT_UNUSED(name);
GIT_UNUSED(regexp);
return config_error_readonly();
}
static int config_snapshot_delete(git_config_backend *cfg, const char *name)
{
GIT_UNUSED(cfg);
GIT_UNUSED(name);
return config_error_readonly();
}
static int config_snapshot_lock(git_config_backend *_cfg)
{
GIT_UNUSED(_cfg);
return config_error_readonly();
}
static int config_snapshot_unlock(git_config_backend *_cfg, int success)
{
GIT_UNUSED(_cfg);
GIT_UNUSED(success);
return config_error_readonly();
}
static void config_snapshot_free(git_config_backend *_backend)
{
config_snapshot_backend *backend = GIT_CONTAINER_OF(_backend, config_snapshot_backend, parent);
if (backend == NULL)
return;
git_config_entries_free(backend->entries);
git_mutex_free(&backend->values_mutex);
git__free(backend);
}
static int config_snapshot_open(git_config_backend *cfg, git_config_level_t level, const git_repository *repo)
{
config_snapshot_backend *b = GIT_CONTAINER_OF(cfg, config_snapshot_backend, parent);
git_config_entries *entries = NULL;
git_config_iterator *it = NULL;
git_config_entry *entry;
int error;
/* We're just copying data, don't care about the level or repo*/
GIT_UNUSED(level);
GIT_UNUSED(repo);
if ((error = git_config_entries_new(&entries)) < 0 ||
(error = b->source->iterator(&it, b->source)) < 0)
goto out;
while ((error = git_config_next(&entry, it)) == 0)
if ((error = git_config_entries_dup_entry(entries, entry)) < 0)
goto out;
if (error < 0) {
if (error != GIT_ITEROVER)
goto out;
error = 0;
}
b->entries = entries;
out:
git_config_iterator_free(it);
if (error)
git_config_entries_free(entries);
return error;
}
int git_config_backend_snapshot(git_config_backend **out, git_config_backend *source)
{
config_snapshot_backend *backend;
backend = git__calloc(1, sizeof(config_snapshot_backend));
GIT_ERROR_CHECK_ALLOC(backend);
backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
git_mutex_init(&backend->values_mutex);
backend->source = source;
backend->parent.readonly = 1;
backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
backend->parent.open = config_snapshot_open;
backend->parent.get = config_snapshot_get;
backend->parent.set = config_snapshot_set;
backend->parent.set_multivar = config_snapshot_set_multivar;
backend->parent.snapshot = git_config_backend_snapshot;
backend->parent.del = config_snapshot_delete;
backend->parent.del_multivar = config_snapshot_delete_multivar;
backend->parent.iterator = config_snapshot_iterator;
backend->parent.lock = config_snapshot_lock;
backend->parent.unlock = config_snapshot_unlock;
backend->parent.free = config_snapshot_free;
*out = &backend->parent;
return 0;
}
+413
View File
@@ -0,0 +1,413 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "git2/attr.h"
#include "git2/blob.h"
#include "git2/index.h"
#include "git2/sys/filter.h"
#include "futils.h"
#include "hash.h"
#include "filter.h"
#include "buf_text.h"
#include "repository.h"
typedef enum {
GIT_CRLF_UNDEFINED,
GIT_CRLF_BINARY,
GIT_CRLF_TEXT,
GIT_CRLF_TEXT_INPUT,
GIT_CRLF_TEXT_CRLF,
GIT_CRLF_AUTO,
GIT_CRLF_AUTO_INPUT,
GIT_CRLF_AUTO_CRLF,
} git_crlf_t;
struct crlf_attrs {
int attr_action; /* the .gitattributes setting */
int crlf_action; /* the core.autocrlf setting */
int auto_crlf;
int safe_crlf;
int core_eol;
};
struct crlf_filter {
git_filter f;
};
static git_crlf_t check_crlf(const char *value)
{
if (GIT_ATTR_IS_TRUE(value))
return GIT_CRLF_TEXT;
else if (GIT_ATTR_IS_FALSE(value))
return GIT_CRLF_BINARY;
else if (GIT_ATTR_IS_UNSPECIFIED(value))
;
else if (strcmp(value, "input") == 0)
return GIT_CRLF_TEXT_INPUT;
else if (strcmp(value, "auto") == 0)
return GIT_CRLF_AUTO;
return GIT_CRLF_UNDEFINED;
}
static git_configmap_value check_eol(const char *value)
{
if (GIT_ATTR_IS_UNSPECIFIED(value))
;
else if (strcmp(value, "lf") == 0)
return GIT_EOL_LF;
else if (strcmp(value, "crlf") == 0)
return GIT_EOL_CRLF;
return GIT_EOL_UNSET;
}
static int has_cr_in_index(const git_filter_source *src)
{
git_repository *repo = git_filter_source_repo(src);
const char *path = git_filter_source_path(src);
git_index *index;
const git_index_entry *entry;
git_blob *blob;
const void *blobcontent;
git_object_size_t blobsize;
bool found_cr;
if (!path)
return false;
if (git_repository_index__weakptr(&index, repo) < 0) {
git_error_clear();
return false;
}
if (!(entry = git_index_get_bypath(index, path, 0)) &&
!(entry = git_index_get_bypath(index, path, 1)))
return false;
if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
return true;
if (git_blob_lookup(&blob, repo, &entry->id) < 0)
return false;
blobcontent = git_blob_rawcontent(blob);
blobsize = git_blob_rawsize(blob);
if (!git__is_sizet(blobsize))
blobsize = (size_t)-1;
found_cr = (blobcontent != NULL &&
blobsize > 0 &&
memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
git_blob_free(blob);
return found_cr;
}
static int text_eol_is_crlf(struct crlf_attrs *ca)
{
if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
return 1;
else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
return 0;
if (ca->core_eol == GIT_EOL_CRLF)
return 1;
if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF)
return 1;
return 0;
}
static git_configmap_value output_eol(struct crlf_attrs *ca)
{
switch (ca->crlf_action) {
case GIT_CRLF_BINARY:
return GIT_EOL_UNSET;
case GIT_CRLF_TEXT_CRLF:
return GIT_EOL_CRLF;
case GIT_CRLF_TEXT_INPUT:
return GIT_EOL_LF;
case GIT_CRLF_UNDEFINED:
case GIT_CRLF_AUTO_CRLF:
return GIT_EOL_CRLF;
case GIT_CRLF_AUTO_INPUT:
return GIT_EOL_LF;
case GIT_CRLF_TEXT:
case GIT_CRLF_AUTO:
return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF;
}
/* TODO: warn when available */
return ca->core_eol;
}
GIT_INLINE(int) check_safecrlf(
struct crlf_attrs *ca,
const git_filter_source *src,
git_buf_text_stats *stats)
{
const char *filename = git_filter_source_path(src);
if (!ca->safe_crlf)
return 0;
if (output_eol(ca) == GIT_EOL_LF) {
/*
* CRLFs would not be restored by checkout:
* check if we'd remove CRLFs
*/
if (stats->crlf) {
if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) {
/* TODO: issue a warning when available */
} else {
if (filename && *filename)
git_error_set(
GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'",
filename);
else
git_error_set(
GIT_ERROR_FILTER, "CRLF would be replaced by LF");
return -1;
}
}
} else if (output_eol(ca) == GIT_EOL_CRLF) {
/*
* CRLFs would be added by checkout:
* check if we have "naked" LFs
*/
if (stats->crlf != stats->lf) {
if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) {
/* TODO: issue a warning when available */
} else {
if (filename && *filename)
git_error_set(
GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'",
filename);
else
git_error_set(
GIT_ERROR_FILTER, "LF would be replaced by CRLF");
return -1;
}
}
}
return 0;
}
static int crlf_apply_to_odb(
struct crlf_attrs *ca,
git_buf *to,
const git_buf *from,
const git_filter_source *src)
{
git_buf_text_stats stats;
bool is_binary;
int error;
/* Binary attribute? Empty file? Nothing to do */
if (ca->crlf_action == GIT_CRLF_BINARY || !git_buf_len(from))
return GIT_PASSTHROUGH;
is_binary = git_buf_text_gather_stats(&stats, from, false);
/* Heuristics to see if we can skip the conversion.
* Straight from Core Git.
*/
if (ca->crlf_action == GIT_CRLF_AUTO ||
ca->crlf_action == GIT_CRLF_AUTO_INPUT ||
ca->crlf_action == GIT_CRLF_AUTO_CRLF) {
if (is_binary)
return GIT_PASSTHROUGH;
/*
* 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(src))
return GIT_PASSTHROUGH;
}
if ((error = check_safecrlf(ca, src, &stats)) < 0)
return error;
/* If there are no CR characters to filter out, then just pass */
if (!stats.crlf)
return GIT_PASSTHROUGH;
/* Actually drop the carriage returns */
return git_buf_text_crlf_to_lf(to, from);
}
static int crlf_apply_to_workdir(
struct crlf_attrs *ca,
git_buf *to,
const git_buf *from)
{
git_buf_text_stats stats;
bool is_binary;
/* Empty file? Nothing to do. */
if (git_buf_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF)
return GIT_PASSTHROUGH;
is_binary = git_buf_text_gather_stats(&stats, from, false);
/* If there are no LFs, or all LFs are part of a CRLF, nothing to do */
if (stats.lf == 0 || stats.lf == stats.crlf)
return GIT_PASSTHROUGH;
if (ca->crlf_action == GIT_CRLF_AUTO ||
ca->crlf_action == GIT_CRLF_AUTO_INPUT ||
ca->crlf_action == GIT_CRLF_AUTO_CRLF) {
/* If we have any existing CR or CRLF line endings, do nothing */
if (stats.cr > 0)
return GIT_PASSTHROUGH;
/* Don't filter binary files */
if (is_binary)
return GIT_PASSTHROUGH;
}
return git_buf_text_lf_to_crlf(to, from);
}
static int convert_attrs(
struct crlf_attrs *ca,
const char **attr_values,
const git_filter_source *src)
{
int error;
memset(ca, 0, sizeof(struct crlf_attrs));
if ((error = git_repository__configmap_lookup(&ca->auto_crlf,
git_filter_source_repo(src), GIT_CONFIGMAP_AUTO_CRLF)) < 0 ||
(error = git_repository__configmap_lookup(&ca->safe_crlf,
git_filter_source_repo(src), GIT_CONFIGMAP_SAFE_CRLF)) < 0 ||
(error = git_repository__configmap_lookup(&ca->core_eol,
git_filter_source_repo(src), GIT_CONFIGMAP_EOL)) < 0)
return error;
/* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) &&
ca->safe_crlf == GIT_SAFE_CRLF_FAIL)
ca->safe_crlf = GIT_SAFE_CRLF_WARN;
if (attr_values) {
/* load the text attribute */
ca->crlf_action = check_crlf(attr_values[2]); /* text */
if (ca->crlf_action == GIT_CRLF_UNDEFINED)
ca->crlf_action = check_crlf(attr_values[0]); /* crlf */
if (ca->crlf_action != GIT_CRLF_BINARY) {
/* load the eol attribute */
int eol_attr = check_eol(attr_values[1]);
if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF)
ca->crlf_action = GIT_CRLF_AUTO_INPUT;
else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF)
ca->crlf_action = GIT_CRLF_AUTO_CRLF;
else if (eol_attr == GIT_EOL_LF)
ca->crlf_action = GIT_CRLF_TEXT_INPUT;
else if (eol_attr == GIT_EOL_CRLF)
ca->crlf_action = GIT_CRLF_TEXT_CRLF;
}
ca->attr_action = ca->crlf_action;
} else {
ca->crlf_action = GIT_CRLF_UNDEFINED;
}
if (ca->crlf_action == GIT_CRLF_TEXT)
ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT;
if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE)
ca->crlf_action = GIT_CRLF_BINARY;
if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
ca->crlf_action = GIT_CRLF_AUTO_CRLF;
if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
ca->crlf_action = GIT_CRLF_AUTO_INPUT;
return 0;
}
static int crlf_check(
git_filter *self,
void **payload, /* points to NULL ptr on entry, may be set */
const git_filter_source *src,
const char **attr_values)
{
struct crlf_attrs ca;
GIT_UNUSED(self);
convert_attrs(&ca, attr_values, src);
if (ca.crlf_action == GIT_CRLF_BINARY)
return GIT_PASSTHROUGH;
*payload = git__malloc(sizeof(ca));
GIT_ERROR_CHECK_ALLOC(*payload);
memcpy(*payload, &ca, sizeof(ca));
return 0;
}
static int crlf_apply(
git_filter *self,
void **payload, /* may be read and/or set */
git_buf *to,
const git_buf *from,
const git_filter_source *src)
{
/* initialize payload in case `check` was bypassed */
if (!*payload) {
int error = crlf_check(self, payload, src, NULL);
if (error < 0)
return error;
}
if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
return crlf_apply_to_workdir(*payload, to, from);
else
return crlf_apply_to_odb(*payload, to, from, src);
}
static void crlf_cleanup(
git_filter *self,
void *payload)
{
GIT_UNUSED(self);
git__free(payload);
}
git_filter *git_crlf_filter_new(void)
{
struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
if (f == NULL)
return NULL;
f->f.version = GIT_FILTER_VERSION;
f->f.attributes = "crlf eol text";
f->f.initialize = NULL;
f->f.shutdown = git_filter_free;
f->f.check = crlf_check;
f->f.apply = crlf_apply;
f->f.cleanup = crlf_cleanup;
return (git_filter *)f;
}
+904
View File
@@ -0,0 +1,904 @@
/*
* GIT - The information manager from hell
*
* Copyright (C) Linus Torvalds, 2005
*/
#include "common.h"
#ifndef GIT_WIN32
#include <sys/time.h>
#endif
#include "util.h"
#include "cache.h"
#include "posix.h"
#include <ctype.h>
#include <time.h>
typedef enum {
DATE_NORMAL = 0,
DATE_RELATIVE,
DATE_SHORT,
DATE_LOCAL,
DATE_ISO8601,
DATE_RFC2822,
DATE_RAW
} date_mode;
/*
* This is like mktime, but without normalization of tm_wday and tm_yday.
*/
static git_time_t tm_to_time_t(const struct tm *tm)
{
static const int mdays[] = {
0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
};
int year = tm->tm_year - 70;
int month = tm->tm_mon;
int day = tm->tm_mday;
if (year < 0 || year > 129) /* algo only works for 1970-2099 */
return -1;
if (month < 0 || month > 11) /* array bounds */
return -1;
if (month < 2 || (year + 2) % 4)
day--;
if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
return -1;
return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24*60*60UL +
tm->tm_hour * 60*60 + tm->tm_min * 60 + tm->tm_sec;
}
static const char *month_names[] = {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
};
static const char *weekday_names[] = {
"Sundays", "Mondays", "Tuesdays", "Wednesdays", "Thursdays", "Fridays", "Saturdays"
};
/*
* Check these. And note how it doesn't do the summer-time conversion.
*
* In my world, it's always summer, and things are probably a bit off
* in other ways too.
*/
static const struct {
const char *name;
int offset;
int dst;
} timezone_names[] = {
{ "IDLW", -12, 0, }, /* International Date Line West */
{ "NT", -11, 0, }, /* Nome */
{ "CAT", -10, 0, }, /* Central Alaska */
{ "HST", -10, 0, }, /* Hawaii Standard */
{ "HDT", -10, 1, }, /* Hawaii Daylight */
{ "YST", -9, 0, }, /* Yukon Standard */
{ "YDT", -9, 1, }, /* Yukon Daylight */
{ "PST", -8, 0, }, /* Pacific Standard */
{ "PDT", -8, 1, }, /* Pacific Daylight */
{ "MST", -7, 0, }, /* Mountain Standard */
{ "MDT", -7, 1, }, /* Mountain Daylight */
{ "CST", -6, 0, }, /* Central Standard */
{ "CDT", -6, 1, }, /* Central Daylight */
{ "EST", -5, 0, }, /* Eastern Standard */
{ "EDT", -5, 1, }, /* Eastern Daylight */
{ "AST", -3, 0, }, /* Atlantic Standard */
{ "ADT", -3, 1, }, /* Atlantic Daylight */
{ "WAT", -1, 0, }, /* West Africa */
{ "GMT", 0, 0, }, /* Greenwich Mean */
{ "UTC", 0, 0, }, /* Universal (Coordinated) */
{ "Z", 0, 0, }, /* Zulu, alias for UTC */
{ "WET", 0, 0, }, /* Western European */
{ "BST", 0, 1, }, /* British Summer */
{ "CET", +1, 0, }, /* Central European */
{ "MET", +1, 0, }, /* Middle European */
{ "MEWT", +1, 0, }, /* Middle European Winter */
{ "MEST", +1, 1, }, /* Middle European Summer */
{ "CEST", +1, 1, }, /* Central European Summer */
{ "MESZ", +1, 1, }, /* Middle European Summer */
{ "FWT", +1, 0, }, /* French Winter */
{ "FST", +1, 1, }, /* French Summer */
{ "EET", +2, 0, }, /* Eastern Europe */
{ "EEST", +2, 1, }, /* Eastern European Daylight */
{ "WAST", +7, 0, }, /* West Australian Standard */
{ "WADT", +7, 1, }, /* West Australian Daylight */
{ "CCT", +8, 0, }, /* China Coast */
{ "JST", +9, 0, }, /* Japan Standard */
{ "EAST", +10, 0, }, /* Eastern Australian Standard */
{ "EADT", +10, 1, }, /* Eastern Australian Daylight */
{ "GST", +10, 0, }, /* Guam Standard */
{ "NZT", +12, 0, }, /* New Zealand */
{ "NZST", +12, 0, }, /* New Zealand Standard */
{ "NZDT", +12, 1, }, /* New Zealand Daylight */
{ "IDLE", +12, 0, }, /* International Date Line East */
};
static size_t match_string(const char *date, const char *str)
{
size_t i = 0;
for (i = 0; *date; date++, str++, i++) {
if (*date == *str)
continue;
if (toupper(*date) == toupper(*str))
continue;
if (!isalnum(*date))
break;
return 0;
}
return i;
}
static int skip_alpha(const char *date)
{
int i = 0;
do {
i++;
} while (isalpha(date[i]));
return i;
}
/*
* Parse month, weekday, or timezone name
*/
static size_t match_alpha(const char *date, struct tm *tm, int *offset)
{
unsigned int i;
for (i = 0; i < 12; i++) {
size_t match = match_string(date, month_names[i]);
if (match >= 3) {
tm->tm_mon = i;
return match;
}
}
for (i = 0; i < 7; i++) {
size_t match = match_string(date, weekday_names[i]);
if (match >= 3) {
tm->tm_wday = i;
return match;
}
}
for (i = 0; i < ARRAY_SIZE(timezone_names); i++) {
size_t match = match_string(date, timezone_names[i].name);
if (match >= 3 || match == strlen(timezone_names[i].name)) {
int off = timezone_names[i].offset;
/* This is bogus, but we like summer */
off += timezone_names[i].dst;
/* Only use the tz name offset if we don't have anything better */
if (*offset == -1)
*offset = 60*off;
return match;
}
}
if (match_string(date, "PM") == 2) {
tm->tm_hour = (tm->tm_hour % 12) + 12;
return 2;
}
if (match_string(date, "AM") == 2) {
tm->tm_hour = (tm->tm_hour % 12) + 0;
return 2;
}
/* BAD */
return skip_alpha(date);
}
static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, struct tm *tm)
{
if (month > 0 && month < 13 && day > 0 && day < 32) {
struct tm check = *tm;
struct tm *r = (now_tm ? &check : tm);
time_t specified;
r->tm_mon = month - 1;
r->tm_mday = day;
if (year == -1) {
if (!now_tm)
return 1;
r->tm_year = now_tm->tm_year;
}
else if (year >= 1970 && year < 2100)
r->tm_year = year - 1900;
else if (year > 70 && year < 100)
r->tm_year = year;
else if (year < 38)
r->tm_year = year + 100;
else
return 0;
if (!now_tm)
return 1;
specified = tm_to_time_t(r);
/* Be it commit time or author time, it does not make
* sense to specify timestamp way into the future. Make
* sure it is not later than ten days from now...
*/
if (now + 10*24*3600 < specified)
return 0;
tm->tm_mon = r->tm_mon;
tm->tm_mday = r->tm_mday;
if (year != -1)
tm->tm_year = r->tm_year;
return 1;
}
return 0;
}
static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm)
{
time_t now;
struct tm now_tm;
struct tm *refuse_future;
long num2, num3;
num2 = strtol(end+1, &end, 10);
num3 = -1;
if (*end == c && isdigit(end[1]))
num3 = strtol(end+1, &end, 10);
/* Time? Date? */
switch (c) {
case ':':
if (num3 < 0)
num3 = 0;
if (num < 25 && num2 >= 0 && num2 < 60 && num3 >= 0 && num3 <= 60) {
tm->tm_hour = num;
tm->tm_min = num2;
tm->tm_sec = num3;
break;
}
return 0;
case '-':
case '/':
case '.':
now = time(NULL);
refuse_future = NULL;
if (p_gmtime_r(&now, &now_tm))
refuse_future = &now_tm;
if (num > 70) {
/* yyyy-mm-dd? */
if (is_date(num, num2, num3, refuse_future, now, tm))
break;
/* yyyy-dd-mm? */
if (is_date(num, num3, num2, refuse_future, now, tm))
break;
}
/* Our eastern European friends say dd.mm.yy[yy]
* is the norm there, so giving precedence to
* mm/dd/yy[yy] form only when separator is not '.'
*/
if (c != '.' &&
is_date(num3, num, num2, refuse_future, now, tm))
break;
/* European dd.mm.yy[yy] or funny US dd/mm/yy[yy] */
if (is_date(num3, num2, num, refuse_future, now, tm))
break;
/* Funny European mm.dd.yy */
if (c == '.' &&
is_date(num3, num, num2, refuse_future, now, tm))
break;
return 0;
}
return end - date;
}
/*
* Have we filled in any part of the time/date yet?
* We just do a binary 'and' to see if the sign bit
* is set in all the values.
*/
static int nodate(struct tm *tm)
{
return (tm->tm_year &
tm->tm_mon &
tm->tm_mday &
tm->tm_hour &
tm->tm_min &
tm->tm_sec) < 0;
}
/*
* We've seen a digit. Time? Year? Date?
*/
static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
{
size_t n;
char *end;
unsigned long num;
num = strtoul(date, &end, 10);
/*
* Seconds since 1970? We trigger on that for any numbers with
* more than 8 digits. This is because we don't want to rule out
* numbers like 20070606 as a YYYYMMDD date.
*/
if (num >= 100000000 && nodate(tm)) {
time_t time = num;
if (p_gmtime_r(&time, tm)) {
*tm_gmt = 1;
return end - date;
}
}
/*
* Check for special formats: num[-.:/]num[same]num
*/
switch (*end) {
case ':':
case '.':
case '/':
case '-':
if (isdigit(end[1])) {
size_t match = match_multi_number(num, *end, date, end, tm);
if (match)
return match;
}
}
/*
* None of the special formats? Try to guess what
* the number meant. We use the number of digits
* to make a more educated guess..
*/
n = 0;
do {
n++;
} while (isdigit(date[n]));
/* Four-digit year or a timezone? */
if (n == 4) {
if (num <= 1400 && *offset == -1) {
unsigned int minutes = num % 100;
unsigned int hours = num / 100;
*offset = hours*60 + minutes;
} else if (num > 1900 && num < 2100)
tm->tm_year = num - 1900;
return n;
}
/*
* Ignore lots of numerals. We took care of 4-digit years above.
* Days or months must be one or two digits.
*/
if (n > 2)
return n;
/*
* NOTE! We will give precedence to day-of-month over month or
* year numbers in the 1-12 range. So 05 is always "mday 5",
* unless we already have a mday..
*
* IOW, 01 Apr 05 parses as "April 1st, 2005".
*/
if (num > 0 && num < 32 && tm->tm_mday < 0) {
tm->tm_mday = num;
return n;
}
/* Two-digit year? */
if (n == 2 && tm->tm_year < 0) {
if (num < 10 && tm->tm_mday >= 0) {
tm->tm_year = num + 100;
return n;
}
if (num >= 70) {
tm->tm_year = num;
return n;
}
}
if (num > 0 && num < 13 && tm->tm_mon < 0)
tm->tm_mon = num-1;
return n;
}
static size_t match_tz(const char *date, int *offp)
{
char *end;
int hour = strtoul(date + 1, &end, 10);
size_t n = end - (date + 1);
int min = 0;
if (n == 4) {
/* hhmm */
min = hour % 100;
hour = hour / 100;
} else if (n != 2) {
min = 99; /* random stuff */
} else if (*end == ':') {
/* hh:mm? */
min = strtoul(end + 1, &end, 10);
if (end - (date + 1) != 5)
min = 99; /* random stuff */
} /* otherwise we parsed "hh" */
/*
* Don't accept any random stuff. Even though some places have
* offset larger than 12 hours (e.g. Pacific/Kiritimati is at
* UTC+14), there is something wrong if hour part is much
* larger than that. We might also want to check that the
* minutes are divisible by 15 or something too. (Offset of
* Kathmandu, Nepal is UTC+5:45)
*/
if (min < 60 && hour < 24) {
int offset = hour * 60 + min;
if (*date == '-')
offset = -offset;
*offp = offset;
}
return end - date;
}
/*
* Parse a string like "0 +0000" as ancient timestamp near epoch, but
* only when it appears not as part of any other string.
*/
static int match_object_header_date(const char *date, git_time_t *timestamp, int *offset)
{
char *end;
unsigned long stamp;
int ofs;
if (*date < '0' || '9' <= *date)
return -1;
stamp = strtoul(date, &end, 10);
if (*end != ' ' || stamp == ULONG_MAX || (end[1] != '+' && end[1] != '-'))
return -1;
date = end + 2;
ofs = strtol(date, &end, 10);
if ((*end != '\0' && (*end != '\n')) || end != date + 4)
return -1;
ofs = (ofs / 100) * 60 + (ofs % 100);
if (date[-1] == '-')
ofs = -ofs;
*timestamp = stamp;
*offset = ofs;
return 0;
}
/* Gr. strptime is crap for this; it doesn't have a way to require RFC2822
(i.e. English) day/month names, and it doesn't work correctly with %z. */
static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset)
{
struct tm tm;
int tm_gmt;
git_time_t dummy_timestamp;
int dummy_offset;
if (!timestamp)
timestamp = &dummy_timestamp;
if (!offset)
offset = &dummy_offset;
memset(&tm, 0, sizeof(tm));
tm.tm_year = -1;
tm.tm_mon = -1;
tm.tm_mday = -1;
tm.tm_isdst = -1;
tm.tm_hour = -1;
tm.tm_min = -1;
tm.tm_sec = -1;
*offset = -1;
tm_gmt = 0;
if (*date == '@' &&
!match_object_header_date(date + 1, timestamp, offset))
return 0; /* success */
for (;;) {
size_t match = 0;
unsigned char c = *date;
/* Stop at end of string or newline */
if (!c || c == '\n')
break;
if (isalpha(c))
match = match_alpha(date, &tm, offset);
else if (isdigit(c))
match = match_digit(date, &tm, offset, &tm_gmt);
else if ((c == '-' || c == '+') && isdigit(date[1]))
match = match_tz(date, offset);
if (!match) {
/* BAD */
match = 1;
}
date += match;
}
/* mktime uses local timezone */
*timestamp = tm_to_time_t(&tm);
if (*offset == -1)
*offset = (int)((time_t)*timestamp - mktime(&tm)) / 60;
if (*timestamp == (git_time_t)-1)
return -1;
if (!tm_gmt)
*timestamp -= *offset * 60;
return 0; /* success */
}
/*
* Relative time update (eg "2 days ago"). If we haven't set the time
* yet, we need to set it from current time.
*/
static git_time_t update_tm(struct tm *tm, struct tm *now, unsigned long sec)
{
time_t n;
if (tm->tm_mday < 0)
tm->tm_mday = now->tm_mday;
if (tm->tm_mon < 0)
tm->tm_mon = now->tm_mon;
if (tm->tm_year < 0) {
tm->tm_year = now->tm_year;
if (tm->tm_mon > now->tm_mon)
tm->tm_year--;
}
n = mktime(tm) - sec;
p_localtime_r(&n, tm);
return n;
}
static void date_now(struct tm *tm, struct tm *now, int *num)
{
GIT_UNUSED(num);
update_tm(tm, now, 0);
}
static void date_yesterday(struct tm *tm, struct tm *now, int *num)
{
GIT_UNUSED(num);
update_tm(tm, now, 24*60*60);
}
static void date_time(struct tm *tm, struct tm *now, int hour)
{
if (tm->tm_hour < hour)
date_yesterday(tm, now, NULL);
tm->tm_hour = hour;
tm->tm_min = 0;
tm->tm_sec = 0;
}
static void date_midnight(struct tm *tm, struct tm *now, int *num)
{
GIT_UNUSED(num);
date_time(tm, now, 0);
}
static void date_noon(struct tm *tm, struct tm *now, int *num)
{
GIT_UNUSED(num);
date_time(tm, now, 12);
}
static void date_tea(struct tm *tm, struct tm *now, int *num)
{
GIT_UNUSED(num);
date_time(tm, now, 17);
}
static void date_pm(struct tm *tm, struct tm *now, int *num)
{
int hour, n = *num;
*num = 0;
GIT_UNUSED(now);
hour = tm->tm_hour;
if (n) {
hour = n;
tm->tm_min = 0;
tm->tm_sec = 0;
}
tm->tm_hour = (hour % 12) + 12;
}
static void date_am(struct tm *tm, struct tm *now, int *num)
{
int hour, n = *num;
*num = 0;
GIT_UNUSED(now);
hour = tm->tm_hour;
if (n) {
hour = n;
tm->tm_min = 0;
tm->tm_sec = 0;
}
tm->tm_hour = (hour % 12);
}
static void date_never(struct tm *tm, struct tm *now, int *num)
{
time_t n = 0;
GIT_UNUSED(now);
GIT_UNUSED(num);
p_localtime_r(&n, tm);
}
static const struct special {
const char *name;
void (*fn)(struct tm *, struct tm *, int *);
} special[] = {
{ "yesterday", date_yesterday },
{ "noon", date_noon },
{ "midnight", date_midnight },
{ "tea", date_tea },
{ "PM", date_pm },
{ "AM", date_am },
{ "never", date_never },
{ "now", date_now },
{ NULL }
};
static const char *number_name[] = {
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine", "ten",
};
static const struct typelen {
const char *type;
int length;
} typelen[] = {
{ "seconds", 1 },
{ "minutes", 60 },
{ "hours", 60*60 },
{ "days", 24*60*60 },
{ "weeks", 7*24*60*60 },
{ NULL }
};
static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm *now, int *num, int *touched)
{
const struct typelen *tl;
const struct special *s;
const char *end = date;
int i;
while (isalpha(*++end))
/* scan to non-alpha */;
for (i = 0; i < 12; i++) {
size_t match = match_string(date, month_names[i]);
if (match >= 3) {
tm->tm_mon = i;
*touched = 1;
return end;
}
}
for (s = special; s->name; s++) {
size_t len = strlen(s->name);
if (match_string(date, s->name) == len) {
s->fn(tm, now, num);
*touched = 1;
return end;
}
}
if (!*num) {
for (i = 1; i < 11; i++) {
size_t len = strlen(number_name[i]);
if (match_string(date, number_name[i]) == len) {
*num = i;
*touched = 1;
return end;
}
}
if (match_string(date, "last") == 4) {
*num = 1;
*touched = 1;
}
return end;
}
tl = typelen;
while (tl->type) {
size_t len = strlen(tl->type);
if (match_string(date, tl->type) >= len-1) {
update_tm(tm, now, tl->length * *num);
*num = 0;
*touched = 1;
return end;
}
tl++;
}
for (i = 0; i < 7; i++) {
size_t match = match_string(date, weekday_names[i]);
if (match >= 3) {
int diff, n = *num -1;
*num = 0;
diff = tm->tm_wday - i;
if (diff <= 0)
n++;
diff += 7*n;
update_tm(tm, now, diff * 24 * 60 * 60);
*touched = 1;
return end;
}
}
if (match_string(date, "months") >= 5) {
int n;
update_tm(tm, now, 0); /* fill in date fields if needed */
n = tm->tm_mon - *num;
*num = 0;
while (n < 0) {
n += 12;
tm->tm_year--;
}
tm->tm_mon = n;
*touched = 1;
return end;
}
if (match_string(date, "years") >= 4) {
update_tm(tm, now, 0); /* fill in date fields if needed */
tm->tm_year -= *num;
*num = 0;
*touched = 1;
return end;
}
return end;
}
static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
{
char *end;
unsigned long number = strtoul(date, &end, 10);
switch (*end) {
case ':':
case '.':
case '/':
case '-':
if (isdigit(end[1])) {
size_t match = match_multi_number(number, *end, date, end, tm);
if (match)
return date + match;
}
}
/* Accept zero-padding only for small numbers ("Dec 02", never "Dec 0002") */
if (date[0] != '0' || end - date <= 2)
*num = number;
return end;
}
/*
* Do we have a pending number at the end, or when
* we see a new one? Let's assume it's a month day,
* as in "Dec 6, 1992"
*/
static void pending_number(struct tm *tm, int *num)
{
int number = *num;
if (number) {
*num = 0;
if (tm->tm_mday < 0 && number < 32)
tm->tm_mday = number;
else if (tm->tm_mon < 0 && number < 13)
tm->tm_mon = number-1;
else if (tm->tm_year < 0) {
if (number > 1969 && number < 2100)
tm->tm_year = number - 1900;
else if (number > 69 && number < 100)
tm->tm_year = number;
else if (number < 38)
tm->tm_year = 100 + number;
/* We mess up for number = 00 ? */
}
}
}
static git_time_t approxidate_str(const char *date,
time_t time_sec,
int *error_ret)
{
int number = 0;
int touched = 0;
struct tm tm = {0}, now;
p_localtime_r(&time_sec, &tm);
now = tm;
tm.tm_year = -1;
tm.tm_mon = -1;
tm.tm_mday = -1;
for (;;) {
unsigned char c = *date;
if (!c)
break;
date++;
if (isdigit(c)) {
pending_number(&tm, &number);
date = approxidate_digit(date-1, &tm, &number);
touched = 1;
continue;
}
if (isalpha(c))
date = approxidate_alpha(date-1, &tm, &now, &number, &touched);
}
pending_number(&tm, &number);
if (!touched)
*error_ret = 1;
return update_tm(&tm, &now, 0);
}
int git__date_parse(git_time_t *out, const char *date)
{
time_t time_sec;
git_time_t timestamp;
int offset, error_ret=0;
if (!parse_date_basic(date, &timestamp, &offset)) {
*out = timestamp;
return 0;
}
if (time(&time_sec) == -1)
return -1;
*out = approxidate_str(date, time_sec, &error_ret);
return error_ret;
}
int git__date_rfc2822_fmt(char *out, size_t len, const git_time *date)
{
int written;
struct tm gmt;
time_t t;
assert(out && date);
t = (time_t) (date->time + date->offset * 60);
if (p_gmtime_r (&t, &gmt) == NULL)
return -1;
written = p_snprintf(out, len, "%.3s, %u %.3s %.4u %02u:%02u:%02u %+03d%02d",
weekday_names[gmt.tm_wday],
gmt.tm_mday,
month_names[gmt.tm_mon],
gmt.tm_year + 1900,
gmt.tm_hour, gmt.tm_min, gmt.tm_sec,
date->offset / 60, date->offset % 60);
if (written < 0 || (written > (int) len - 1))
return -1;
return 0;
}
+628
View File
@@ -0,0 +1,628 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "delta.h"
/* maximum hash entry list for the same hash bucket */
#define HASH_LIMIT 64
#define RABIN_SHIFT 23
#define RABIN_WINDOW 16
static const unsigned int T[256] = {
0x00000000, 0xab59b4d1, 0x56b369a2, 0xfdeadd73, 0x063f6795, 0xad66d344,
0x508c0e37, 0xfbd5bae6, 0x0c7ecf2a, 0xa7277bfb, 0x5acda688, 0xf1941259,
0x0a41a8bf, 0xa1181c6e, 0x5cf2c11d, 0xf7ab75cc, 0x18fd9e54, 0xb3a42a85,
0x4e4ef7f6, 0xe5174327, 0x1ec2f9c1, 0xb59b4d10, 0x48719063, 0xe32824b2,
0x1483517e, 0xbfdae5af, 0x423038dc, 0xe9698c0d, 0x12bc36eb, 0xb9e5823a,
0x440f5f49, 0xef56eb98, 0x31fb3ca8, 0x9aa28879, 0x6748550a, 0xcc11e1db,
0x37c45b3d, 0x9c9defec, 0x6177329f, 0xca2e864e, 0x3d85f382, 0x96dc4753,
0x6b369a20, 0xc06f2ef1, 0x3bba9417, 0x90e320c6, 0x6d09fdb5, 0xc6504964,
0x2906a2fc, 0x825f162d, 0x7fb5cb5e, 0xd4ec7f8f, 0x2f39c569, 0x846071b8,
0x798aaccb, 0xd2d3181a, 0x25786dd6, 0x8e21d907, 0x73cb0474, 0xd892b0a5,
0x23470a43, 0x881ebe92, 0x75f463e1, 0xdeadd730, 0x63f67950, 0xc8afcd81,
0x354510f2, 0x9e1ca423, 0x65c91ec5, 0xce90aa14, 0x337a7767, 0x9823c3b6,
0x6f88b67a, 0xc4d102ab, 0x393bdfd8, 0x92626b09, 0x69b7d1ef, 0xc2ee653e,
0x3f04b84d, 0x945d0c9c, 0x7b0be704, 0xd05253d5, 0x2db88ea6, 0x86e13a77,
0x7d348091, 0xd66d3440, 0x2b87e933, 0x80de5de2, 0x7775282e, 0xdc2c9cff,
0x21c6418c, 0x8a9ff55d, 0x714a4fbb, 0xda13fb6a, 0x27f92619, 0x8ca092c8,
0x520d45f8, 0xf954f129, 0x04be2c5a, 0xafe7988b, 0x5432226d, 0xff6b96bc,
0x02814bcf, 0xa9d8ff1e, 0x5e738ad2, 0xf52a3e03, 0x08c0e370, 0xa39957a1,
0x584ced47, 0xf3155996, 0x0eff84e5, 0xa5a63034, 0x4af0dbac, 0xe1a96f7d,
0x1c43b20e, 0xb71a06df, 0x4ccfbc39, 0xe79608e8, 0x1a7cd59b, 0xb125614a,
0x468e1486, 0xedd7a057, 0x103d7d24, 0xbb64c9f5, 0x40b17313, 0xebe8c7c2,
0x16021ab1, 0xbd5bae60, 0x6cb54671, 0xc7ecf2a0, 0x3a062fd3, 0x915f9b02,
0x6a8a21e4, 0xc1d39535, 0x3c394846, 0x9760fc97, 0x60cb895b, 0xcb923d8a,
0x3678e0f9, 0x9d215428, 0x66f4eece, 0xcdad5a1f, 0x3047876c, 0x9b1e33bd,
0x7448d825, 0xdf116cf4, 0x22fbb187, 0x89a20556, 0x7277bfb0, 0xd92e0b61,
0x24c4d612, 0x8f9d62c3, 0x7836170f, 0xd36fa3de, 0x2e857ead, 0x85dcca7c,
0x7e09709a, 0xd550c44b, 0x28ba1938, 0x83e3ade9, 0x5d4e7ad9, 0xf617ce08,
0x0bfd137b, 0xa0a4a7aa, 0x5b711d4c, 0xf028a99d, 0x0dc274ee, 0xa69bc03f,
0x5130b5f3, 0xfa690122, 0x0783dc51, 0xacda6880, 0x570fd266, 0xfc5666b7,
0x01bcbbc4, 0xaae50f15, 0x45b3e48d, 0xeeea505c, 0x13008d2f, 0xb85939fe,
0x438c8318, 0xe8d537c9, 0x153feaba, 0xbe665e6b, 0x49cd2ba7, 0xe2949f76,
0x1f7e4205, 0xb427f6d4, 0x4ff24c32, 0xe4abf8e3, 0x19412590, 0xb2189141,
0x0f433f21, 0xa41a8bf0, 0x59f05683, 0xf2a9e252, 0x097c58b4, 0xa225ec65,
0x5fcf3116, 0xf49685c7, 0x033df00b, 0xa86444da, 0x558e99a9, 0xfed72d78,
0x0502979e, 0xae5b234f, 0x53b1fe3c, 0xf8e84aed, 0x17bea175, 0xbce715a4,
0x410dc8d7, 0xea547c06, 0x1181c6e0, 0xbad87231, 0x4732af42, 0xec6b1b93,
0x1bc06e5f, 0xb099da8e, 0x4d7307fd, 0xe62ab32c, 0x1dff09ca, 0xb6a6bd1b,
0x4b4c6068, 0xe015d4b9, 0x3eb80389, 0x95e1b758, 0x680b6a2b, 0xc352defa,
0x3887641c, 0x93ded0cd, 0x6e340dbe, 0xc56db96f, 0x32c6cca3, 0x999f7872,
0x6475a501, 0xcf2c11d0, 0x34f9ab36, 0x9fa01fe7, 0x624ac294, 0xc9137645,
0x26459ddd, 0x8d1c290c, 0x70f6f47f, 0xdbaf40ae, 0x207afa48, 0x8b234e99,
0x76c993ea, 0xdd90273b, 0x2a3b52f7, 0x8162e626, 0x7c883b55, 0xd7d18f84,
0x2c043562, 0x875d81b3, 0x7ab75cc0, 0xd1eee811
};
static const unsigned int U[256] = {
0x00000000, 0x7eb5200d, 0x5633f4cb, 0x2886d4c6, 0x073e5d47, 0x798b7d4a,
0x510da98c, 0x2fb88981, 0x0e7cba8e, 0x70c99a83, 0x584f4e45, 0x26fa6e48,
0x0942e7c9, 0x77f7c7c4, 0x5f711302, 0x21c4330f, 0x1cf9751c, 0x624c5511,
0x4aca81d7, 0x347fa1da, 0x1bc7285b, 0x65720856, 0x4df4dc90, 0x3341fc9d,
0x1285cf92, 0x6c30ef9f, 0x44b63b59, 0x3a031b54, 0x15bb92d5, 0x6b0eb2d8,
0x4388661e, 0x3d3d4613, 0x39f2ea38, 0x4747ca35, 0x6fc11ef3, 0x11743efe,
0x3eccb77f, 0x40799772, 0x68ff43b4, 0x164a63b9, 0x378e50b6, 0x493b70bb,
0x61bda47d, 0x1f088470, 0x30b00df1, 0x4e052dfc, 0x6683f93a, 0x1836d937,
0x250b9f24, 0x5bbebf29, 0x73386bef, 0x0d8d4be2, 0x2235c263, 0x5c80e26e,
0x740636a8, 0x0ab316a5, 0x2b7725aa, 0x55c205a7, 0x7d44d161, 0x03f1f16c,
0x2c4978ed, 0x52fc58e0, 0x7a7a8c26, 0x04cfac2b, 0x73e5d470, 0x0d50f47d,
0x25d620bb, 0x5b6300b6, 0x74db8937, 0x0a6ea93a, 0x22e87dfc, 0x5c5d5df1,
0x7d996efe, 0x032c4ef3, 0x2baa9a35, 0x551fba38, 0x7aa733b9, 0x041213b4,
0x2c94c772, 0x5221e77f, 0x6f1ca16c, 0x11a98161, 0x392f55a7, 0x479a75aa,
0x6822fc2b, 0x1697dc26, 0x3e1108e0, 0x40a428ed, 0x61601be2, 0x1fd53bef,
0x3753ef29, 0x49e6cf24, 0x665e46a5, 0x18eb66a8, 0x306db26e, 0x4ed89263,
0x4a173e48, 0x34a21e45, 0x1c24ca83, 0x6291ea8e, 0x4d29630f, 0x339c4302,
0x1b1a97c4, 0x65afb7c9, 0x446b84c6, 0x3adea4cb, 0x1258700d, 0x6ced5000,
0x4355d981, 0x3de0f98c, 0x15662d4a, 0x6bd30d47, 0x56ee4b54, 0x285b6b59,
0x00ddbf9f, 0x7e689f92, 0x51d01613, 0x2f65361e, 0x07e3e2d8, 0x7956c2d5,
0x5892f1da, 0x2627d1d7, 0x0ea10511, 0x7014251c, 0x5facac9d, 0x21198c90,
0x099f5856, 0x772a785b, 0x4c921c31, 0x32273c3c, 0x1aa1e8fa, 0x6414c8f7,
0x4bac4176, 0x3519617b, 0x1d9fb5bd, 0x632a95b0, 0x42eea6bf, 0x3c5b86b2,
0x14dd5274, 0x6a687279, 0x45d0fbf8, 0x3b65dbf5, 0x13e30f33, 0x6d562f3e,
0x506b692d, 0x2ede4920, 0x06589de6, 0x78edbdeb, 0x5755346a, 0x29e01467,
0x0166c0a1, 0x7fd3e0ac, 0x5e17d3a3, 0x20a2f3ae, 0x08242768, 0x76910765,
0x59298ee4, 0x279caee9, 0x0f1a7a2f, 0x71af5a22, 0x7560f609, 0x0bd5d604,
0x235302c2, 0x5de622cf, 0x725eab4e, 0x0ceb8b43, 0x246d5f85, 0x5ad87f88,
0x7b1c4c87, 0x05a96c8a, 0x2d2fb84c, 0x539a9841, 0x7c2211c0, 0x029731cd,
0x2a11e50b, 0x54a4c506, 0x69998315, 0x172ca318, 0x3faa77de, 0x411f57d3,
0x6ea7de52, 0x1012fe5f, 0x38942a99, 0x46210a94, 0x67e5399b, 0x19501996,
0x31d6cd50, 0x4f63ed5d, 0x60db64dc, 0x1e6e44d1, 0x36e89017, 0x485db01a,
0x3f77c841, 0x41c2e84c, 0x69443c8a, 0x17f11c87, 0x38499506, 0x46fcb50b,
0x6e7a61cd, 0x10cf41c0, 0x310b72cf, 0x4fbe52c2, 0x67388604, 0x198da609,
0x36352f88, 0x48800f85, 0x6006db43, 0x1eb3fb4e, 0x238ebd5d, 0x5d3b9d50,
0x75bd4996, 0x0b08699b, 0x24b0e01a, 0x5a05c017, 0x728314d1, 0x0c3634dc,
0x2df207d3, 0x534727de, 0x7bc1f318, 0x0574d315, 0x2acc5a94, 0x54797a99,
0x7cffae5f, 0x024a8e52, 0x06852279, 0x78300274, 0x50b6d6b2, 0x2e03f6bf,
0x01bb7f3e, 0x7f0e5f33, 0x57888bf5, 0x293dabf8, 0x08f998f7, 0x764cb8fa,
0x5eca6c3c, 0x207f4c31, 0x0fc7c5b0, 0x7172e5bd, 0x59f4317b, 0x27411176,
0x1a7c5765, 0x64c97768, 0x4c4fa3ae, 0x32fa83a3, 0x1d420a22, 0x63f72a2f,
0x4b71fee9, 0x35c4dee4, 0x1400edeb, 0x6ab5cde6, 0x42331920, 0x3c86392d,
0x133eb0ac, 0x6d8b90a1, 0x450d4467, 0x3bb8646a
};
struct index_entry {
const unsigned char *ptr;
unsigned int val;
struct index_entry *next;
};
struct git_delta_index {
unsigned long memsize;
const void *src_buf;
size_t src_size;
unsigned int hash_mask;
struct index_entry *hash[GIT_FLEX_ARRAY];
};
static int lookup_index_alloc(
void **out, unsigned long *out_len, size_t entries, size_t hash_count)
{
size_t entries_len, hash_len, index_len;
GIT_ERROR_CHECK_ALLOC_MULTIPLY(&entries_len, entries, sizeof(struct index_entry));
GIT_ERROR_CHECK_ALLOC_MULTIPLY(&hash_len, hash_count, sizeof(struct index_entry *));
GIT_ERROR_CHECK_ALLOC_ADD(&index_len, sizeof(struct git_delta_index), entries_len);
GIT_ERROR_CHECK_ALLOC_ADD(&index_len, index_len, hash_len);
if (!git__is_ulong(index_len)) {
git_error_set(GIT_ERROR_NOMEMORY, "overly large delta");
return -1;
}
*out = git__malloc(index_len);
GIT_ERROR_CHECK_ALLOC(*out);
*out_len = (unsigned long)index_len;
return 0;
}
int git_delta_index_init(
git_delta_index **out, const void *buf, size_t bufsize)
{
unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
const unsigned char *data, *buffer = buf;
struct git_delta_index *index;
struct index_entry *entry, **hash;
void *mem;
unsigned long memsize;
*out = NULL;
if (!buf || !bufsize)
return 0;
/* Determine index hash size. Note that indexing skips the
first byte to allow for optimizing the rabin polynomial
initialization in create_delta(). */
entries = (unsigned int)(bufsize - 1) / RABIN_WINDOW;
if (bufsize >= 0xffffffffUL) {
/*
* Current delta format can't encode offsets into
* reference buffer with more than 32 bits.
*/
entries = 0xfffffffeU / RABIN_WINDOW;
}
hsize = entries / 4;
for (i = 4; i < 31 && (1u << i) < hsize; i++);
hsize = 1 << i;
hmask = hsize - 1;
if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0)
return -1;
index = mem;
mem = index->hash;
hash = mem;
mem = hash + hsize;
entry = mem;
index->memsize = memsize;
index->src_buf = buf;
index->src_size = bufsize;
index->hash_mask = hmask;
memset(hash, 0, hsize * sizeof(*hash));
/* allocate an array to count hash entries */
hash_count = git__calloc(hsize, sizeof(*hash_count));
if (!hash_count) {
git__free(index);
return -1;
}
/* then populate the index */
prev_val = ~0;
for (data = buffer + entries * RABIN_WINDOW - RABIN_WINDOW;
data >= buffer;
data -= RABIN_WINDOW) {
unsigned int val = 0;
for (i = 1; i <= RABIN_WINDOW; i++)
val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
if (val == prev_val) {
/* keep the lowest of consecutive identical blocks */
entry[-1].ptr = data + RABIN_WINDOW;
} else {
prev_val = val;
i = val & hmask;
entry->ptr = data + RABIN_WINDOW;
entry->val = val;
entry->next = hash[i];
hash[i] = entry++;
hash_count[i]++;
}
}
/*
* Determine a limit on the number of entries in the same hash
* bucket. This guard us against patological data sets causing
* really bad hash distribution with most entries in the same hash
* bucket that would bring us to O(m*n) computing costs (m and n
* corresponding to reference and target buffer sizes).
*
* Make sure none of the hash buckets has more entries than
* we're willing to test. Otherwise we cull the entry list
* uniformly to still preserve a good repartition across
* the reference buffer.
*/
for (i = 0; i < hsize; i++) {
if (hash_count[i] < HASH_LIMIT)
continue;
entry = hash[i];
do {
struct index_entry *keep = entry;
int skip = hash_count[i] / HASH_LIMIT / 2;
do {
entry = entry->next;
} while(--skip && entry);
keep->next = entry;
} while (entry);
}
git__free(hash_count);
*out = index;
return 0;
}
void git_delta_index_free(git_delta_index *index)
{
git__free(index);
}
size_t git_delta_index_size(git_delta_index *index)
{
assert(index);
return index->memsize;
}
/*
* The maximum size for any opcode sequence, including the initial header
* plus rabin window plus biggest copy.
*/
#define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7)
int git_delta_create_from_index(
void **out,
size_t *out_len,
const struct git_delta_index *index,
const void *trg_buf,
size_t trg_size,
size_t max_size)
{
unsigned int i, bufpos, bufsize, moff, msize, val;
int inscnt;
const unsigned char *ref_data, *ref_top, *data, *top;
unsigned char *buf;
*out = NULL;
*out_len = 0;
if (!trg_buf || !trg_size)
return 0;
if (index->src_size > UINT_MAX ||
trg_size > UINT_MAX ||
max_size > (UINT_MAX - MAX_OP_SIZE - 1)) {
git_error_set(GIT_ERROR_INVALID, "buffer sizes too large for delta processing");
return -1;
}
bufpos = 0;
bufsize = 8192;
if (max_size && bufsize >= max_size)
bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
buf = git__malloc(bufsize);
GIT_ERROR_CHECK_ALLOC(buf);
/* store reference buffer size */
i = (unsigned int)index->src_size;
while (i >= 0x80) {
buf[bufpos++] = i | 0x80;
i >>= 7;
}
buf[bufpos++] = i;
/* store target buffer size */
i = (unsigned int)trg_size;
while (i >= 0x80) {
buf[bufpos++] = i | 0x80;
i >>= 7;
}
buf[bufpos++] = i;
ref_data = index->src_buf;
ref_top = ref_data + index->src_size;
data = trg_buf;
top = (const unsigned char *) trg_buf + trg_size;
bufpos++;
val = 0;
for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) {
buf[bufpos++] = *data;
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
}
inscnt = i;
moff = 0;
msize = 0;
while (data < top) {
if (msize < 4096) {
struct index_entry *entry;
val ^= U[data[-RABIN_WINDOW]];
val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
i = val & index->hash_mask;
for (entry = index->hash[i]; entry; entry = entry->next) {
const unsigned char *ref = entry->ptr;
const unsigned char *src = data;
unsigned int ref_size = (unsigned int)(ref_top - ref);
if (entry->val != val)
continue;
if (ref_size > (unsigned int)(top - src))
ref_size = (unsigned int)(top - src);
if (ref_size <= msize)
break;
while (ref_size-- && *src++ == *ref)
ref++;
if (msize < (unsigned int)(ref - entry->ptr)) {
/* this is our best match so far */
msize = (unsigned int)(ref - entry->ptr);
moff = (unsigned int)(entry->ptr - ref_data);
if (msize >= 4096) /* good enough */
break;
}
}
}
if (msize < 4) {
if (!inscnt)
bufpos++;
buf[bufpos++] = *data++;
inscnt++;
if (inscnt == 0x7f) {
buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
msize = 0;
} else {
unsigned int left;
unsigned char *op;
if (inscnt) {
while (moff && ref_data[moff-1] == data[-1]) {
/* we can match one byte back */
msize++;
moff--;
data--;
bufpos--;
if (--inscnt)
continue;
bufpos--; /* remove count slot */
inscnt--; /* make it -1 */
break;
}
buf[bufpos - inscnt - 1] = inscnt;
inscnt = 0;
}
/* A copy op is currently limited to 64KB (pack v2) */
left = (msize < 0x10000) ? 0 : (msize - 0x10000);
msize -= left;
op = buf + bufpos++;
i = 0x80;
if (moff & 0x000000ff)
buf[bufpos++] = moff >> 0, i |= 0x01;
if (moff & 0x0000ff00)
buf[bufpos++] = moff >> 8, i |= 0x02;
if (moff & 0x00ff0000)
buf[bufpos++] = moff >> 16, i |= 0x04;
if (moff & 0xff000000)
buf[bufpos++] = moff >> 24, i |= 0x08;
if (msize & 0x00ff)
buf[bufpos++] = msize >> 0, i |= 0x10;
if (msize & 0xff00)
buf[bufpos++] = msize >> 8, i |= 0x20;
*op = i;
data += msize;
moff += msize;
msize = left;
if (msize < 4096) {
int j;
val = 0;
for (j = -RABIN_WINDOW; j < 0; j++)
val = ((val << 8) | data[j])
^ T[val >> RABIN_SHIFT];
}
}
if (bufpos >= bufsize - MAX_OP_SIZE) {
void *tmp = buf;
bufsize = bufsize * 3 / 2;
if (max_size && bufsize >= max_size)
bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1);
if (max_size && bufpos > max_size)
break;
buf = git__realloc(buf, bufsize);
if (!buf) {
git__free(tmp);
return -1;
}
}
}
if (inscnt)
buf[bufpos - inscnt - 1] = inscnt;
if (max_size && bufpos > max_size) {
git_error_set(GIT_ERROR_NOMEMORY, "delta would be larger than maximum size");
git__free(buf);
return GIT_EBUFS;
}
*out_len = bufpos;
*out = buf;
return 0;
}
/*
* Delta application was heavily cribbed from BinaryDelta.java in JGit, which
* itself was heavily cribbed from <code>patch-delta.c</code> in the
* GIT project. The original delta patching code was written by
* Nicolas Pitre <nico@cam.org>.
*/
static int hdr_sz(
size_t *size,
const unsigned char **delta,
const unsigned char *end)
{
const unsigned char *d = *delta;
size_t r = 0;
unsigned int c, shift = 0;
do {
if (d == end) {
git_error_set(GIT_ERROR_INVALID, "truncated delta");
return -1;
}
c = *d++;
r |= (c & 0x7f) << shift;
shift += 7;
} while (c & 0x80);
*delta = d;
*size = r;
return 0;
}
int git_delta_read_header(
size_t *base_out,
size_t *result_out,
const unsigned char *delta,
size_t delta_len)
{
const unsigned char *delta_end = delta + delta_len;
if ((hdr_sz(base_out, &delta, delta_end) < 0) ||
(hdr_sz(result_out, &delta, delta_end) < 0))
return -1;
return 0;
}
#define DELTA_HEADER_BUFFER_LEN 16
int git_delta_read_header_fromstream(
size_t *base_sz, size_t *res_sz, git_packfile_stream *stream)
{
static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN;
unsigned char buffer[DELTA_HEADER_BUFFER_LEN];
const unsigned char *delta, *delta_end;
size_t len;
ssize_t read;
len = read = 0;
while (len < buffer_len) {
read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len);
if (read == 0)
break;
if (read == GIT_EBUFS)
continue;
len += read;
}
delta = buffer;
delta_end = delta + len;
if ((hdr_sz(base_sz, &delta, delta_end) < 0) ||
(hdr_sz(res_sz, &delta, delta_end) < 0))
return -1;
return 0;
}
int git_delta_apply(
void **out,
size_t *out_len,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len)
{
const unsigned char *delta_end = delta + delta_len;
size_t base_sz, res_sz, alloc_sz;
unsigned char *res_dp;
*out = NULL;
*out_len = 0;
/*
* Check that the base size matches the data we were given;
* 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)) {
git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data");
return -1;
}
if (hdr_sz(&res_sz, &delta, delta_end) < 0) {
git_error_set(GIT_ERROR_INVALID, "failed to apply delta: base size does not match given data");
return -1;
}
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1);
res_dp = git__malloc(alloc_sz);
GIT_ERROR_CHECK_ALLOC(res_dp);
res_dp[res_sz] = '\0';
*out = res_dp;
*out_len = res_sz;
while (delta < delta_end) {
unsigned char cmd = *delta++;
if (cmd & 0x80) {
/* cmd is a copy instruction; copy from the base. */
size_t off = 0, len = 0, end;
#define ADD_DELTA(o, shift) { if (delta < delta_end) (o) |= ((unsigned) *delta++ << shift); else goto fail; }
if (cmd & 0x01) ADD_DELTA(off, 0UL);
if (cmd & 0x02) ADD_DELTA(off, 8UL);
if (cmd & 0x04) ADD_DELTA(off, 16UL);
if (cmd & 0x08) ADD_DELTA(off, 24UL);
if (cmd & 0x10) ADD_DELTA(len, 0UL);
if (cmd & 0x20) ADD_DELTA(len, 8UL);
if (cmd & 0x40) ADD_DELTA(len, 16UL);
if (!len) len = 0x10000;
#undef ADD_DELTA
if (GIT_ADD_SIZET_OVERFLOW(&end, off, len) ||
base_len < end || res_sz < len)
goto fail;
memcpy(res_dp, base + off, len);
res_dp += len;
res_sz -= len;
} else if (cmd) {
/*
* cmd is a literal insert instruction; copy from
* the delta stream itself.
*/
if (delta_end - delta < cmd || res_sz < cmd)
goto fail;
memcpy(res_dp, delta, cmd);
delta += cmd;
res_dp += cmd;
res_sz -= cmd;
} else {
/* cmd == 0 is reserved for future encodings. */
goto fail;
}
}
if (delta != delta_end || res_sz)
goto fail;
return 0;
fail:
git__free(*out);
*out = NULL;
*out_len = 0;
git_error_set(GIT_ERROR_INVALID, "failed to apply delta");
return -1;
}
+136
View File
@@ -0,0 +1,136 @@
/*
* diff-delta code taken from git.git. See diff-delta.c for details.
*
*/
#ifndef INCLUDE_git_delta_h__
#define INCLUDE_git_delta_h__
#include "common.h"
#include "pack.h"
typedef struct git_delta_index git_delta_index;
/*
* git_delta_index_init: compute index data from given buffer
*
* This returns a pointer to a struct delta_index that should be passed to
* subsequent create_delta() calls, or to free_delta_index(). A NULL pointer
* is returned on failure. The given buffer must not be freed nor altered
* before free_delta_index() is called. The returned pointer must be freed
* using free_delta_index().
*/
extern int git_delta_index_init(
git_delta_index **out, const void *buf, size_t bufsize);
/*
* Free the index created by git_delta_index_init()
*/
extern void git_delta_index_free(git_delta_index *index);
/*
* Returns memory usage of delta index.
*/
extern size_t git_delta_index_size(git_delta_index *index);
/*
* create_delta: create a delta from given index for the given buffer
*
* This function may be called multiple times with different buffers using
* the same delta_index pointer. If max_delta_size is non-zero and the
* resulting delta is to be larger than max_delta_size then NULL is returned.
* On success, a non-NULL pointer to the buffer with the delta data is
* returned and *delta_size is updated with its size. The returned buffer
* must be freed by the caller.
*/
extern int git_delta_create_from_index(
void **out,
size_t *out_size,
const struct git_delta_index *index,
const void *buf,
size_t bufsize,
size_t max_delta_size);
/*
* diff_delta: create a delta from source buffer to target buffer
*
* If max_delta_size is non-zero and the resulting delta is to be larger
* than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL
* pointer to the buffer with the delta data is returned and *delta_size is
* updated with its size. The returned buffer must be freed by the caller.
*/
GIT_INLINE(int) git_delta(
void **out, size_t *out_len,
const void *src_buf, size_t src_bufsize,
const void *trg_buf, size_t trg_bufsize,
size_t max_delta_size)
{
git_delta_index *index;
int error = 0;
*out = NULL;
*out_len = 0;
if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0)
return error;
if (index) {
error = git_delta_create_from_index(out, out_len,
index, trg_buf, trg_bufsize, max_delta_size);
git_delta_index_free(index);
}
return error;
}
/* the smallest possible delta size is 4 bytes */
#define GIT_DELTA_SIZE_MIN 4
/**
* Apply a git binary delta to recover the original content.
* The caller is responsible for freeing the returned buffer.
*
* @param out the output buffer
* @param out_len the length of the output buffer
* @param base the base to copy from during copy instructions.
* @param base_len number of bytes available at base.
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return 0 on success or an error code
*/
extern int git_delta_apply(
void **out,
size_t *out_len,
const unsigned char *base,
size_t base_len,
const unsigned char *delta,
size_t delta_len);
/**
* Read the header of a git binary delta.
*
* @param base_out pointer to store the base size field.
* @param result_out pointer to store the result size field.
* @param delta the delta to execute copy/insert instructions from.
* @param delta_len total number of bytes in the delta.
* @return 0 on success or an error code
*/
extern int git_delta_read_header(
size_t *base_out,
size_t *result_out,
const unsigned char *delta,
size_t delta_len);
/**
* Read the header of a git binary delta
*
* This variant reads just enough from the packfile stream to read the
* delta header.
*/
extern int git_delta_read_header_fromstream(
size_t *base_out,
size_t *result_out,
git_packfile_stream *stream);
#endif
+894
View File
@@ -0,0 +1,894 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "git2/describe.h"
#include "git2/strarray.h"
#include "git2/diff.h"
#include "git2/status.h"
#include "commit.h"
#include "commit_list.h"
#include "oidmap.h"
#include "refs.h"
#include "repository.h"
#include "revwalk.h"
#include "tag.h"
#include "vector.h"
#include "wildmatch.h"
/* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */
struct commit_name {
git_tag *tag;
unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
unsigned name_checked:1;
git_oid sha1;
char *path;
/* Khash workaround. They original key has to still be reachable */
git_oid peeled;
};
static void *oidmap_value_bykey(git_oidmap *map, const git_oid *key)
{
return git_oidmap_get(map, key);
}
static struct commit_name *find_commit_name(
git_oidmap *names,
const git_oid *peeled)
{
return (struct commit_name *)(oidmap_value_bykey(names, peeled));
}
static int replace_name(
git_tag **tag,
git_repository *repo,
struct commit_name *e,
unsigned int prio,
const git_oid *sha1)
{
git_time_t e_time = 0, t_time = 0;
if (!e || e->prio < prio)
return 1;
if (e->prio == 2 && prio == 2) {
/* Multiple annotated tags point to the same commit.
* Select one to keep based upon their tagger date.
*/
git_tag *t = NULL;
if (!e->tag) {
if (git_tag_lookup(&t, repo, &e->sha1) < 0)
return 1;
e->tag = t;
}
if (git_tag_lookup(&t, repo, sha1) < 0)
return 0;
*tag = t;
if (e->tag->tagger)
e_time = e->tag->tagger->when.time;
if (t->tagger)
t_time = t->tagger->when.time;
if (e_time < t_time)
return 1;
}
return 0;
}
static int add_to_known_names(
git_repository *repo,
git_oidmap *names,
const char *path,
const git_oid *peeled,
unsigned int prio,
const git_oid *sha1)
{
struct commit_name *e = find_commit_name(names, peeled);
bool found = (e != NULL);
git_tag *tag = NULL;
if (replace_name(&tag, repo, e, prio, sha1)) {
if (!found) {
e = git__malloc(sizeof(struct commit_name));
GIT_ERROR_CHECK_ALLOC(e);
e->path = NULL;
e->tag = NULL;
}
if (e->tag)
git_tag_free(e->tag);
e->tag = tag;
e->prio = prio;
e->name_checked = 0;
git_oid_cpy(&e->sha1, sha1);
git__free(e->path);
e->path = git__strdup(path);
git_oid_cpy(&e->peeled, peeled);
if (!found && git_oidmap_set(names, &e->peeled, e) < 0)
return -1;
}
else
git_tag_free(tag);
return 0;
}
static int retrieve_peeled_tag_or_object_oid(
git_oid *peeled_out,
git_oid *ref_target_out,
git_repository *repo,
const char *refname)
{
git_reference *ref;
git_object *peeled = NULL;
int error;
if ((error = git_reference_lookup_resolved(&ref, repo, refname, -1)) < 0)
return error;
if ((error = git_reference_peel(&peeled, ref, GIT_OBJECT_ANY)) < 0)
goto cleanup;
git_oid_cpy(ref_target_out, git_reference_target(ref));
git_oid_cpy(peeled_out, git_object_id(peeled));
if (git_oid_cmp(ref_target_out, peeled_out) != 0)
error = 1; /* The reference was pointing to a annotated tag */
else
error = 0; /* Any other object */
cleanup:
git_reference_free(ref);
git_object_free(peeled);
return error;
}
struct git_describe_result {
int dirty;
int exact_match;
int fallback_to_id;
git_oid commit_id;
git_repository *repo;
struct commit_name *name;
struct possible_tag *tag;
};
struct get_name_data
{
git_describe_options *opts;
git_repository *repo;
git_oidmap *names;
git_describe_result *result;
};
static int commit_name_dup(struct commit_name **out, struct commit_name *in)
{
struct commit_name *name;
name = git__malloc(sizeof(struct commit_name));
GIT_ERROR_CHECK_ALLOC(name);
memcpy(name, in, sizeof(struct commit_name));
name->tag = NULL;
name->path = NULL;
if (in->tag && git_tag_dup(&name->tag, in->tag) < 0)
return -1;
name->path = git__strdup(in->path);
GIT_ERROR_CHECK_ALLOC(name->path);
*out = name;
return 0;
}
static int get_name(const char *refname, void *payload)
{
struct get_name_data *data;
bool is_tag, is_annotated, all;
git_oid peeled, sha1;
unsigned int prio;
int error = 0;
data = (struct get_name_data *)payload;
is_tag = !git__prefixcmp(refname, GIT_REFS_TAGS_DIR);
all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
/* Reject anything outside refs/tags/ unless --all */
if (!all && !is_tag)
return 0;
/* Accept only tags that match the pattern, if given */
if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern,
refname + strlen(GIT_REFS_TAGS_DIR), 0)))
return 0;
/* Is it annotated? */
if ((error = retrieve_peeled_tag_or_object_oid(
&peeled, &sha1, data->repo, refname)) < 0)
return error;
is_annotated = error;
/*
* By default, we only use annotated tags, but with --tags
* we fall back to lightweight ones (even without --tags,
* we still remember lightweight ones, only to give hints
* in an error message). --all allows any refs to be used.
*/
if (is_annotated)
prio = 2;
else if (is_tag)
prio = 1;
else
prio = 0;
add_to_known_names(data->repo, data->names,
all ? refname + strlen(GIT_REFS_DIR) : refname + strlen(GIT_REFS_TAGS_DIR),
&peeled, prio, &sha1);
return 0;
}
struct possible_tag {
struct commit_name *name;
int depth;
int found_order;
unsigned flag_within;
};
static int possible_tag_dup(struct possible_tag **out, struct possible_tag *in)
{
struct possible_tag *tag;
int error;
tag = git__malloc(sizeof(struct possible_tag));
GIT_ERROR_CHECK_ALLOC(tag);
memcpy(tag, in, sizeof(struct possible_tag));
tag->name = NULL;
if ((error = commit_name_dup(&tag->name, in->name)) < 0) {
git__free(tag);
*out = NULL;
return error;
}
*out = tag;
return 0;
}
static int compare_pt(const void *a_, const void *b_)
{
struct possible_tag *a = (struct possible_tag *)a_;
struct possible_tag *b = (struct possible_tag *)b_;
if (a->depth != b->depth)
return a->depth - b->depth;
if (a->found_order != b->found_order)
return a->found_order - b->found_order;
return 0;
}
#define SEEN (1u << 0)
static unsigned long finish_depth_computation(
git_pqueue *list,
git_revwalk *walk,
struct possible_tag *best)
{
unsigned long seen_commits = 0;
int error, i;
while (git_pqueue_size(list) > 0) {
git_commit_list_node *c = git_pqueue_pop(list);
seen_commits++;
if (c->flags & best->flag_within) {
size_t index = 0;
while (git_pqueue_size(list) > index) {
git_commit_list_node *i = git_pqueue_get(list, index);
if (!(i->flags & best->flag_within))
break;
index++;
}
if (index > git_pqueue_size(list))
break;
} else
best->depth++;
for (i = 0; i < c->out_degree; i++) {
git_commit_list_node *p = c->parents[i];
if ((error = git_commit_list_parse(walk, p)) < 0)
return error;
if (!(p->flags & SEEN))
if ((error = git_pqueue_insert(list, p)) < 0)
return error;
p->flags |= c->flags;
}
}
return seen_commits;
}
static int display_name(git_buf *buf, git_repository *repo, struct commit_name *n)
{
if (n->prio == 2 && !n->tag) {
if (git_tag_lookup(&n->tag, repo, &n->sha1) < 0) {
git_error_set(GIT_ERROR_TAG, "annotated tag '%s' not available", n->path);
return -1;
}
}
if (n->tag && !n->name_checked) {
if (!git_tag_name(n->tag)) {
git_error_set(GIT_ERROR_TAG, "annotated tag '%s' has no embedded name", n->path);
return -1;
}
/* TODO: Cope with warnings
if (strcmp(n->tag->tag, all ? n->path + 5 : n->path))
warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path);
*/
n->name_checked = 1;
}
if (n->tag)
git_buf_printf(buf, "%s", git_tag_name(n->tag));
else
git_buf_printf(buf, "%s", n->path);
return 0;
}
static int find_unique_abbrev_size(
int *out,
git_repository *repo,
const git_oid *oid_in,
unsigned int abbreviated_size)
{
size_t size = abbreviated_size;
git_odb *odb;
git_oid dummy;
int error;
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
return error;
while (size < GIT_OID_HEXSZ) {
if ((error = git_odb_exists_prefix(&dummy, odb, oid_in, size)) == 0) {
*out = (int) size;
return 0;
}
/* If the error wasn't that it's not unique, then it's a proper error */
if (error != GIT_EAMBIGUOUS)
return error;
/* Try again with a larger size */
size++;
}
/* If we didn't find any shorter prefix, we have to do the whole thing */
*out = GIT_OID_HEXSZ;
return 0;
}
static int show_suffix(
git_buf *buf,
int depth,
git_repository *repo,
const git_oid* id,
unsigned int abbrev_size)
{
int error, size = 0;
char hex_oid[GIT_OID_HEXSZ];
if ((error = find_unique_abbrev_size(&size, repo, id, abbrev_size)) < 0)
return error;
git_oid_fmt(hex_oid, id);
git_buf_printf(buf, "-%d-g", depth);
git_buf_put(buf, hex_oid, size);
return git_buf_oom(buf) ? -1 : 0;
}
#define MAX_CANDIDATES_TAGS FLAG_BITS - 1
static int describe_not_found(const git_oid *oid, const char *message_format) {
char oid_str[GIT_OID_HEXSZ + 1];
git_oid_tostr(oid_str, sizeof(oid_str), oid);
git_error_set(GIT_ERROR_DESCRIBE, message_format, oid_str);
return GIT_ENOTFOUND;
}
static int describe(
struct get_name_data *data,
git_commit *commit)
{
struct commit_name *n;
struct possible_tag *best;
bool all, tags;
git_revwalk *walk = NULL;
git_pqueue list;
git_commit_list_node *cmit, *gave_up_on = NULL;
git_vector all_matches = GIT_VECTOR_INIT;
unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
unsigned long seen_commits = 0; /* TODO: Check long */
unsigned int unannotated_cnt = 0;
int error;
if (git_vector_init(&all_matches, MAX_CANDIDATES_TAGS, compare_pt) < 0)
return -1;
if ((error = git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp)) < 0)
goto cleanup;
all = data->opts->describe_strategy == GIT_DESCRIBE_ALL;
tags = data->opts->describe_strategy == GIT_DESCRIBE_TAGS;
git_oid_cpy(&data->result->commit_id, git_commit_id(commit));
n = find_commit_name(data->names, git_commit_id(commit));
if (n && (tags || all || n->prio == 2)) {
/*
* Exact match to an existing ref.
*/
data->result->exact_match = 1;
if ((error = commit_name_dup(&data->result->name, n)) < 0)
goto cleanup;
goto cleanup;
}
if (!data->opts->max_candidates_tags) {
error = describe_not_found(
git_commit_id(commit),
"cannot describe - no tag exactly matches '%s'");
goto cleanup;
}
if ((error = git_revwalk_new(&walk, git_commit_owner(commit))) < 0)
goto cleanup;
if ((cmit = git_revwalk__commit_lookup(walk, git_commit_id(commit))) == NULL)
goto cleanup;
if ((error = git_commit_list_parse(walk, cmit)) < 0)
goto cleanup;
cmit->flags = SEEN;
if ((error = git_pqueue_insert(&list, cmit)) < 0)
goto cleanup;
while (git_pqueue_size(&list) > 0)
{
int i;
git_commit_list_node *c = (git_commit_list_node *)git_pqueue_pop(&list);
seen_commits++;
n = find_commit_name(data->names, &c->oid);
if (n) {
if (!tags && !all && n->prio < 2) {
unannotated_cnt++;
} else if (match_cnt < data->opts->max_candidates_tags) {
struct possible_tag *t = git__malloc(sizeof(struct commit_name));
GIT_ERROR_CHECK_ALLOC(t);
if ((error = git_vector_insert(&all_matches, t)) < 0)
goto cleanup;
match_cnt++;
t->name = n;
t->depth = seen_commits - 1;
t->flag_within = 1u << match_cnt;
t->found_order = match_cnt;
c->flags |= t->flag_within;
if (n->prio == 2)
annotated_cnt++;
}
else {
gave_up_on = c;
break;
}
}
for (cur_match = 0; cur_match < match_cnt; cur_match++) {
struct possible_tag *t = git_vector_get(&all_matches, cur_match);
if (!(c->flags & t->flag_within))
t->depth++;
}
if (annotated_cnt && (git_pqueue_size(&list) == 0)) {
/*
if (debug) {
char oid_str[GIT_OID_HEXSZ + 1];
git_oid_tostr(oid_str, sizeof(oid_str), &c->oid);
fprintf(stderr, "finished search at %s\n", oid_str);
}
*/
break;
}
for (i = 0; i < c->out_degree; i++) {
git_commit_list_node *p = c->parents[i];
if ((error = git_commit_list_parse(walk, p)) < 0)
goto cleanup;
if (!(p->flags & SEEN))
if ((error = git_pqueue_insert(&list, p)) < 0)
goto cleanup;
p->flags |= c->flags;
if (data->opts->only_follow_first_parent)
break;
}
}
if (!match_cnt) {
if (data->opts->show_commit_oid_as_fallback) {
data->result->fallback_to_id = 1;
git_oid_cpy(&data->result->commit_id, &cmit->oid);
goto cleanup;
}
if (unannotated_cnt) {
error = describe_not_found(git_commit_id(commit),
"cannot describe - "
"no annotated tags can describe '%s'; "
"however, there were unannotated tags.");
goto cleanup;
}
else {
error = describe_not_found(git_commit_id(commit),
"cannot describe - "
"no tags can describe '%s'.");
goto cleanup;
}
}
git_vector_sort(&all_matches);
best = (struct possible_tag *)git_vector_get(&all_matches, 0);
if (gave_up_on) {
if ((error = git_pqueue_insert(&list, gave_up_on)) < 0)
goto cleanup;
seen_commits--;
}
if ((error = finish_depth_computation(
&list, walk, best)) < 0)
goto cleanup;
seen_commits += error;
if ((error = possible_tag_dup(&data->result->tag, best)) < 0)
goto cleanup;
/*
{
static const char *prio_names[] = {
"head", "lightweight", "annotated",
};
char oid_str[GIT_OID_HEXSZ + 1];
if (debug) {
for (cur_match = 0; cur_match < match_cnt; cur_match++) {
struct possible_tag *t = (struct possible_tag *)git_vector_get(&all_matches, cur_match);
fprintf(stderr, " %-11s %8d %s\n",
prio_names[t->name->prio],
t->depth, t->name->path);
}
fprintf(stderr, "traversed %lu commits\n", seen_commits);
if (gave_up_on) {
git_oid_tostr(oid_str, sizeof(oid_str), &gave_up_on->oid);
fprintf(stderr,
"more than %i tags found; listed %i most recent\n"
"gave up search at %s\n",
data->opts->max_candidates_tags, data->opts->max_candidates_tags,
oid_str);
}
}
}
*/
git_oid_cpy(&data->result->commit_id, &cmit->oid);
cleanup:
{
size_t i;
struct possible_tag *match;
git_vector_foreach(&all_matches, i, match) {
git__free(match);
}
}
git_vector_free(&all_matches);
git_pqueue_free(&list);
git_revwalk_free(walk);
return error;
}
static int normalize_options(
git_describe_options *dst,
const git_describe_options *src)
{
git_describe_options default_options = GIT_DESCRIBE_OPTIONS_INIT;
if (!src) src = &default_options;
*dst = *src;
if (dst->max_candidates_tags > GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS)
dst->max_candidates_tags = GIT_DESCRIBE_DEFAULT_MAX_CANDIDATES_TAGS;
return 0;
}
int git_describe_commit(
git_describe_result **result,
git_object *committish,
git_describe_options *opts)
{
struct get_name_data data;
struct commit_name *name;
git_commit *commit;
int error = -1;
git_describe_options normalized;
assert(committish);
data.result = git__calloc(1, sizeof(git_describe_result));
GIT_ERROR_CHECK_ALLOC(data.result);
data.result->repo = git_object_owner(committish);
data.repo = git_object_owner(committish);
if ((error = normalize_options(&normalized, opts)) < 0)
return error;
GIT_ERROR_CHECK_VERSION(
&normalized,
GIT_DESCRIBE_OPTIONS_VERSION,
"git_describe_options");
data.opts = &normalized;
if ((error = git_oidmap_new(&data.names)) < 0)
return error;
/** TODO: contains to be implemented */
if ((error = git_object_peel((git_object **)(&commit), committish, GIT_OBJECT_COMMIT)) < 0)
goto cleanup;
if ((error = git_reference_foreach_name(
git_object_owner(committish),
get_name, &data)) < 0)
goto cleanup;
if (git_oidmap_size(data.names) == 0 && !opts->show_commit_oid_as_fallback) {
git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - "
"no reference found, cannot describe anything.");
error = -1;
goto cleanup;
}
if ((error = describe(&data, commit)) < 0)
goto cleanup;
cleanup:
git_commit_free(commit);
git_oidmap_foreach_value(data.names, name, {
git_tag_free(name->tag);
git__free(name->path);
git__free(name);
});
git_oidmap_free(data.names);
if (error < 0)
git_describe_result_free(data.result);
else
*result = data.result;
return error;
}
int git_describe_workdir(
git_describe_result **out,
git_repository *repo,
git_describe_options *opts)
{
int error;
git_oid current_id;
git_status_list *status = NULL;
git_status_options status_opts = GIT_STATUS_OPTIONS_INIT;
git_describe_result *result = NULL;
git_object *commit;
if ((error = git_reference_name_to_id(&current_id, repo, GIT_HEAD_FILE)) < 0)
return error;
if ((error = git_object_lookup(&commit, repo, &current_id, GIT_OBJECT_COMMIT)) < 0)
return error;
/* The first step is to perform a describe of HEAD, so we can leverage this */
if ((error = git_describe_commit(&result, commit, opts)) < 0)
goto out;
if ((error = git_status_list_new(&status, repo, &status_opts)) < 0)
goto out;
if (git_status_list_entrycount(status) > 0)
result->dirty = 1;
out:
git_object_free(commit);
git_status_list_free(status);
if (error < 0)
git_describe_result_free(result);
else
*out = result;
return error;
}
static int normalize_format_options(
git_describe_format_options *dst,
const git_describe_format_options *src)
{
if (!src) {
git_describe_format_options_init(dst, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION);
return 0;
}
memcpy(dst, src, sizeof(git_describe_format_options));
return 0;
}
int git_describe_format(git_buf *out, const git_describe_result *result, const git_describe_format_options *given)
{
int error;
git_repository *repo;
struct commit_name *name;
git_describe_format_options opts;
assert(out && result);
GIT_ERROR_CHECK_VERSION(given, GIT_DESCRIBE_FORMAT_OPTIONS_VERSION, "git_describe_format_options");
normalize_format_options(&opts, given);
git_buf_sanitize(out);
if (opts.always_use_long_format && opts.abbreviated_size == 0) {
git_error_set(GIT_ERROR_DESCRIBE, "cannot describe - "
"'always_use_long_format' is incompatible with a zero"
"'abbreviated_size'");
return -1;
}
repo = result->repo;
/* If we did find an exact match, then it's the easier method */
if (result->exact_match) {
name = result->name;
if ((error = display_name(out, repo, name)) < 0)
return error;
if (opts.always_use_long_format) {
const git_oid *id = name->tag ? git_tag_target_id(name->tag) : &result->commit_id;
if ((error = show_suffix(out, 0, repo, id, opts.abbreviated_size)) < 0)
return error;
}
if (result->dirty && opts.dirty_suffix)
git_buf_puts(out, opts.dirty_suffix);
return git_buf_oom(out) ? -1 : 0;
}
/* If we didn't find *any* tags, we fall back to the commit's id */
if (result->fallback_to_id) {
char hex_oid[GIT_OID_HEXSZ + 1] = {0};
int size = 0;
if ((error = find_unique_abbrev_size(
&size, repo, &result->commit_id, opts.abbreviated_size)) < 0)
return -1;
git_oid_fmt(hex_oid, &result->commit_id);
git_buf_put(out, hex_oid, size);
if (result->dirty && opts.dirty_suffix)
git_buf_puts(out, opts.dirty_suffix);
return git_buf_oom(out) ? -1 : 0;
}
/* Lastly, if we found a matching tag, we show that */
name = result->tag->name;
if ((error = display_name(out, repo, name)) < 0)
return error;
if (opts.abbreviated_size) {
if ((error = show_suffix(out, result->tag->depth, repo,
&result->commit_id, opts.abbreviated_size)) < 0)
return error;
}
if (result->dirty && opts.dirty_suffix) {
git_buf_puts(out, opts.dirty_suffix);
}
return git_buf_oom(out) ? -1 : 0;
}
void git_describe_result_free(git_describe_result *result)
{
if (result == NULL)
return;
if (result->name) {
git_tag_free(result->name->tag);
git__free(result->name->path);
git__free(result->name);
}
if (result->tag) {
git_tag_free(result->tag->name->tag);
git__free(result->tag->name->path);
git__free(result->tag->name);
git__free(result->tag);
}
git__free(result);
}
int git_describe_options_init(git_describe_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_describe_options, GIT_DESCRIBE_OPTIONS_INIT);
return 0;
}
int git_describe_init_options(git_describe_options *opts, unsigned int version)
{
return git_describe_options_init(opts, version);
}
int git_describe_format_options_init(git_describe_format_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_describe_format_options, GIT_DESCRIBE_FORMAT_OPTIONS_INIT);
return 0;
}
int git_describe_init_format_options(git_describe_format_options *opts, unsigned int version)
{
return git_describe_format_options_init(opts, version);
}
+500
View File
@@ -0,0 +1,500 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "diff.h"
#include "git2/version.h"
#include "diff_generate.h"
#include "patch.h"
#include "commit.h"
#include "index.h"
struct patch_id_args {
git_hash_ctx ctx;
git_oid result;
int first_file;
};
GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta)
{
const char *str = delta->old_file.path;
if (!str ||
delta->status == GIT_DELTA_ADDED ||
delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_COPIED)
str = delta->new_file.path;
return str;
}
const char *git_diff_delta__path(const git_diff_delta *delta)
{
return diff_delta__path(delta);
}
int git_diff_delta__cmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff_delta__casecmp(const void *a, const void *b)
{
const git_diff_delta *da = a, *db = b;
int val = strcasecmp(diff_delta__path(da), diff_delta__path(db));
return val ? val : ((int)da->status - (int)db->status);
}
int git_diff__entry_cmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return strcmp(entry_a->path, entry_b->path);
}
int git_diff__entry_icmp(const void *a, const void *b)
{
const git_index_entry *entry_a = a;
const git_index_entry *entry_b = b;
return strcasecmp(entry_a->path, entry_b->path);
}
void git_diff_free(git_diff *diff)
{
if (!diff)
return;
GIT_REFCOUNT_DEC(diff, diff->free_fn);
}
void git_diff_addref(git_diff *diff)
{
GIT_REFCOUNT_INC(diff);
}
size_t git_diff_num_deltas(const git_diff *diff)
{
assert(diff);
return diff->deltas.length;
}
size_t git_diff_num_deltas_of_type(const git_diff *diff, git_delta_t type)
{
size_t i, count = 0;
const git_diff_delta *delta;
assert(diff);
git_vector_foreach(&diff->deltas, i, delta) {
count += (delta->status == type);
}
return count;
}
const git_diff_delta *git_diff_get_delta(const git_diff *diff, size_t idx)
{
assert(diff);
return git_vector_get(&diff->deltas, idx);
}
int git_diff_is_sorted_icase(const git_diff *diff)
{
return (diff->opts.flags & GIT_DIFF_IGNORE_CASE) != 0;
}
int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff)
{
assert(out);
GIT_ERROR_CHECK_VERSION(out, GIT_DIFF_PERFDATA_VERSION, "git_diff_perfdata");
out->stat_calls = diff->perf.stat_calls;
out->oid_calculations = diff->perf.oid_calculations;
return 0;
}
int git_diff_foreach(
git_diff *diff,
git_diff_file_cb file_cb,
git_diff_binary_cb binary_cb,
git_diff_hunk_cb hunk_cb,
git_diff_line_cb data_cb,
void *payload)
{
int error = 0;
git_diff_delta *delta;
size_t idx;
assert(diff);
git_vector_foreach(&diff->deltas, idx, delta) {
git_patch *patch;
/* check flags against patch status */
if (git_diff_delta__should_skip(&diff->opts, delta))
continue;
if ((error = git_patch_from_diff(&patch, diff, idx)) != 0)
break;
error = git_patch__invoke_callbacks(patch, file_cb, binary_cb,
hunk_cb, data_cb, payload);
git_patch_free(patch);
if (error)
break;
}
return error;
}
int git_diff_format_email__append_header_tobuf(
git_buf *out,
const git_oid *id,
const git_signature *author,
const char *summary,
const char *body,
size_t patch_no,
size_t total_patches,
bool exclude_patchno_marker)
{
char idstr[GIT_OID_HEXSZ + 1];
char date_str[GIT_DATE_RFC2822_SZ];
int error = 0;
git_oid_fmt(idstr, id);
idstr[GIT_OID_HEXSZ] = '\0';
if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str),
&author->when)) < 0)
return error;
error = git_buf_printf(out,
"From %s Mon Sep 17 00:00:00 2001\n" \
"From: %s <%s>\n" \
"Date: %s\n" \
"Subject: ",
idstr,
author->name, author->email,
date_str);
if (error < 0)
return error;
if (!exclude_patchno_marker) {
if (total_patches == 1) {
error = git_buf_puts(out, "[PATCH] ");
} else {
error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ",
patch_no, total_patches);
}
if (error < 0)
return error;
}
error = git_buf_printf(out, "%s\n\n", summary);
if (body) {
git_buf_puts(out, body);
if (out->ptr[out->size - 1] != '\n')
git_buf_putc(out, '\n');
}
return error;
}
int git_diff_format_email__append_patches_tobuf(
git_buf *out,
git_diff *diff)
{
size_t i, deltas;
int error = 0;
deltas = git_diff_num_deltas(diff);
for (i = 0; i < deltas; ++i) {
git_patch *patch = NULL;
if ((error = git_patch_from_diff(&patch, diff, i)) >= 0)
error = git_patch_to_buf(out, patch);
git_patch_free(patch);
if (error < 0)
break;
}
return error;
}
int git_diff_format_email(
git_buf *out,
git_diff *diff,
const git_diff_format_email_options *opts)
{
git_diff_stats *stats = NULL;
char *summary = NULL, *loc = NULL;
bool ignore_marker;
unsigned int format_flags = 0;
size_t allocsize;
int error;
assert(out && diff && opts);
assert(opts->summary && opts->id && opts->author);
GIT_ERROR_CHECK_VERSION(opts,
GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION,
"git_format_email_options");
ignore_marker = (opts->flags &
GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0;
if (!ignore_marker) {
if (opts->patch_no > opts->total_patches) {
git_error_set(GIT_ERROR_INVALID,
"patch %"PRIuZ" out of range. max %"PRIuZ,
opts->patch_no, opts->total_patches);
return -1;
}
if (opts->patch_no == 0) {
git_error_set(GIT_ERROR_INVALID,
"invalid patch no %"PRIuZ". should be >0", opts->patch_no);
return -1;
}
}
/* the summary we receive may not be clean.
* it could potentially contain new line characters
* or not be set, sanitize, */
if ((loc = strpbrk(opts->summary, "\r\n")) != NULL) {
size_t offset = 0;
if ((offset = (loc - opts->summary)) == 0) {
git_error_set(GIT_ERROR_INVALID, "summary is empty");
error = -1;
goto on_error;
}
GIT_ERROR_CHECK_ALLOC_ADD(&allocsize, offset, 1);
summary = git__calloc(allocsize, sizeof(char));
GIT_ERROR_CHECK_ALLOC(summary);
strncpy(summary, opts->summary, offset);
}
error = git_diff_format_email__append_header_tobuf(out,
opts->id, opts->author, summary == NULL ? opts->summary : summary,
opts->body, opts->patch_no, opts->total_patches, ignore_marker);
if (error < 0)
goto on_error;
format_flags = GIT_DIFF_STATS_FULL | GIT_DIFF_STATS_INCLUDE_SUMMARY;
if ((error = git_buf_puts(out, "---\n")) < 0 ||
(error = git_diff_get_stats(&stats, diff)) < 0 ||
(error = git_diff_stats_to_buf(out, stats, format_flags, 0)) < 0 ||
(error = git_buf_putc(out, '\n')) < 0 ||
(error = git_diff_format_email__append_patches_tobuf(out, diff)) < 0)
goto on_error;
error = git_buf_puts(out, "--\nlibgit2 " LIBGIT2_VERSION "\n\n");
on_error:
git__free(summary);
git_diff_stats_free(stats);
return error;
}
int git_diff_commit_as_email(
git_buf *out,
git_repository *repo,
git_commit *commit,
size_t patch_no,
size_t total_patches,
uint32_t flags,
const git_diff_options *diff_opts)
{
git_diff *diff = NULL;
git_diff_format_email_options opts =
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT;
int error;
assert (out && repo && commit);
opts.flags = flags;
opts.patch_no = patch_no;
opts.total_patches = total_patches;
opts.id = git_commit_id(commit);
opts.summary = git_commit_summary(commit);
opts.body = git_commit_body(commit);
opts.author = git_commit_author(commit);
if ((error = git_diff__commit(&diff, repo, commit, diff_opts)) < 0)
return error;
error = git_diff_format_email(out, diff, &opts);
git_diff_free(diff);
return error;
}
int git_diff_options_init(git_diff_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_diff_options, GIT_DIFF_OPTIONS_INIT);
return 0;
}
int git_diff_init_options(git_diff_options *opts, unsigned int version)
{
return git_diff_options_init(opts, version);
}
int git_diff_find_options_init(
git_diff_find_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_diff_find_options, GIT_DIFF_FIND_OPTIONS_INIT);
return 0;
}
int git_diff_find_init_options(
git_diff_find_options *opts, unsigned int version)
{
return git_diff_find_options_init(opts, version);
}
int git_diff_format_email_options_init(
git_diff_format_email_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_diff_format_email_options,
GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT);
return 0;
}
int git_diff_format_email_init_options(
git_diff_format_email_options *opts, unsigned int version)
{
return git_diff_format_email_options_init(opts, version);
}
static int flush_hunk(git_oid *result, git_hash_ctx *ctx)
{
git_oid hash;
unsigned short carry = 0;
int error, i;
if ((error = git_hash_final(&hash, ctx)) < 0 ||
(error = git_hash_init(ctx)) < 0)
return error;
for (i = 0; i < GIT_OID_RAWSZ; i++) {
carry += result->id[i] + hash.id[i];
result->id[i] = (unsigned char)carry;
carry >>= 8;
}
return 0;
}
static void strip_spaces(git_buf *buf)
{
char *src = buf->ptr, *dst = buf->ptr;
char c;
size_t len = 0;
while ((c = *src++) != '\0') {
if (!git__isspace(c)) {
*dst++ = c;
len++;
}
}
git_buf_truncate(buf, len);
}
int git_diff_patchid_print_callback__to_buf(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
struct patch_id_args *args = (struct patch_id_args *) payload;
git_buf buf = GIT_BUF_INIT;
int error = 0;
if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL ||
line->origin == GIT_DIFF_LINE_ADD_EOFNL ||
line->origin == GIT_DIFF_LINE_DEL_EOFNL)
goto out;
if ((error = git_diff_print_callback__to_buf(delta, hunk,
line, &buf)) < 0)
goto out;
strip_spaces(&buf);
if (line->origin == GIT_DIFF_LINE_FILE_HDR &&
!args->first_file &&
(error = flush_hunk(&args->result, &args->ctx) < 0))
goto out;
if ((error = git_hash_update(&args->ctx, buf.ptr, buf.size)) < 0)
goto out;
if (line->origin == GIT_DIFF_LINE_FILE_HDR && args->first_file)
args->first_file = 0;
out:
git_buf_dispose(&buf);
return error;
}
int git_diff_patchid_options_init(git_diff_patchid_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_diff_patchid_options, GIT_DIFF_PATCHID_OPTIONS_INIT);
return 0;
}
int git_diff_patchid(git_oid *out, git_diff *diff, git_diff_patchid_options *opts)
{
struct patch_id_args args;
int error;
GIT_ERROR_CHECK_VERSION(
opts, GIT_DIFF_PATCHID_OPTIONS_VERSION, "git_diff_patchid_options");
memset(&args, 0, sizeof(args));
args.first_file = 1;
if ((error = git_hash_ctx_init(&args.ctx)) < 0)
goto out;
if ((error = git_diff_print(diff,
GIT_DIFF_FORMAT_PATCH_ID,
git_diff_patchid_print_callback__to_buf,
&args)) < 0)
goto out;
if ((error = (flush_hunk(&args.result, &args.ctx))) < 0)
goto out;
git_oid_cpy(out, &args.result);
out:
git_hash_ctx_cleanup(&args.ctx);
return error;
}
+69
View File
@@ -0,0 +1,69 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_h__
#define INCLUDE_diff_h__
#include "common.h"
#include "git2/diff.h"
#include "git2/patch.h"
#include "git2/sys/diff.h"
#include "git2/oid.h"
#include <stdio.h>
#include "vector.h"
#include "buffer.h"
#include "iterator.h"
#include "repository.h"
#include "pool.h"
#include "odb.h"
#define DIFF_OLD_PREFIX_DEFAULT "a/"
#define DIFF_NEW_PREFIX_DEFAULT "b/"
typedef enum {
GIT_DIFF_TYPE_UNKNOWN = 0,
GIT_DIFF_TYPE_GENERATED = 1,
GIT_DIFF_TYPE_PARSED = 2,
} git_diff_origin_t;
struct git_diff {
git_refcount rc;
git_repository *repo;
git_attr_session attrsession;
git_diff_origin_t type;
git_diff_options opts;
git_vector deltas; /* vector of git_diff_delta */
git_pool pool;
git_iterator_type_t old_src;
git_iterator_type_t new_src;
git_diff_perfdata perf;
int (*strcomp)(const char *, const char *);
int (*strncomp)(const char *, const char *, size_t);
int (*pfxcomp)(const char *str, const char *pfx);
int (*entrycomp)(const void *a, const void *b);
int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx);
void (*free_fn)(git_diff *diff);
};
extern int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int oid_strlen,
bool print_index);
extern int git_diff_delta__cmp(const void *a, const void *b);
extern int git_diff_delta__casecmp(const void *a, const void *b);
extern int git_diff__entry_cmp(const void *a, const void *b);
extern int git_diff__entry_icmp(const void *a, const void *b);
#endif
+520
View File
@@ -0,0 +1,520 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "diff_driver.h"
#include "git2/attr.h"
#include "common.h"
#include "diff.h"
#include "strmap.h"
#include "map.h"
#include "buf_text.h"
#include "config.h"
#include "regexp.h"
#include "repository.h"
typedef enum {
DIFF_DRIVER_AUTO = 0,
DIFF_DRIVER_BINARY = 1,
DIFF_DRIVER_TEXT = 2,
DIFF_DRIVER_PATTERNLIST = 3,
} git_diff_driver_t;
typedef struct {
git_regexp re;
int flags;
} git_diff_driver_pattern;
enum {
REG_NEGATE = (1 << 15) /* get out of the way of existing flags */
};
/* data for finding function context for a given file type */
struct git_diff_driver {
git_diff_driver_t type;
uint32_t binary_flags;
uint32_t other_flags;
git_array_t(git_diff_driver_pattern) fn_patterns;
git_regexp word_pattern;
char name[GIT_FLEX_ARRAY];
};
#include "userdiff.h"
struct git_diff_driver_registry {
git_strmap *drivers;
};
#define FORCE_DIFFABLE (GIT_DIFF_FORCE_TEXT | GIT_DIFF_FORCE_BINARY)
static git_diff_driver global_drivers[3] = {
{ DIFF_DRIVER_AUTO, 0, 0, },
{ DIFF_DRIVER_BINARY, GIT_DIFF_FORCE_BINARY, 0 },
{ DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 },
};
git_diff_driver_registry *git_diff_driver_registry_new(void)
{
git_diff_driver_registry *reg =
git__calloc(1, sizeof(git_diff_driver_registry));
if (!reg)
return NULL;
if (git_strmap_new(&reg->drivers) < 0) {
git_diff_driver_registry_free(reg);
return NULL;
}
return reg;
}
void git_diff_driver_registry_free(git_diff_driver_registry *reg)
{
git_diff_driver *drv;
if (!reg)
return;
git_strmap_foreach_value(reg->drivers, drv, git_diff_driver_free(drv));
git_strmap_free(reg->drivers);
git__free(reg);
}
static int diff_driver_add_patterns(
git_diff_driver *drv, const char *regex_str, int regex_flags)
{
int error = 0;
const char *scan, *end;
git_diff_driver_pattern *pat = NULL;
git_buf buf = GIT_BUF_INIT;
for (scan = regex_str; scan; scan = end) {
/* get pattern to fill in */
if ((pat = git_array_alloc(drv->fn_patterns)) == NULL) {
return -1;
}
pat->flags = regex_flags;
if (*scan == '!') {
pat->flags |= REG_NEGATE;
++scan;
}
if ((end = strchr(scan, '\n')) != NULL) {
error = git_buf_set(&buf, scan, end - scan);
end++;
} else {
error = git_buf_sets(&buf, scan);
}
if (error < 0)
break;
if ((error = git_regexp_compile(&pat->re, buf.ptr, regex_flags)) != 0) {
/*
* TODO: issue a warning
*/
}
}
if (error && pat != NULL)
(void)git_array_pop(drv->fn_patterns); /* release last item */
git_buf_dispose(&buf);
/* We want to ignore bad patterns, so return success regardless */
return 0;
}
static int diff_driver_xfuncname(const git_config_entry *entry, void *payload)
{
return diff_driver_add_patterns(payload, entry->value, 0);
}
static int diff_driver_funcname(const git_config_entry *entry, void *payload)
{
return diff_driver_add_patterns(payload, entry->value, 0);
}
static git_diff_driver_registry *git_repository_driver_registry(
git_repository *repo)
{
if (!repo->diff_drivers) {
git_diff_driver_registry *reg = git_diff_driver_registry_new();
reg = git__compare_and_swap(&repo->diff_drivers, NULL, reg);
if (reg != NULL) /* if we race, free losing allocation */
git_diff_driver_registry_free(reg);
}
if (!repo->diff_drivers)
git_error_set(GIT_ERROR_REPOSITORY, "unable to create diff driver registry");
return repo->diff_drivers;
}
static int diff_driver_alloc(
git_diff_driver **out, size_t *namelen_out, const char *name)
{
git_diff_driver *driver;
size_t driverlen = sizeof(git_diff_driver),
namelen = strlen(name),
alloclen;
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, driverlen, namelen);
GIT_ERROR_CHECK_ALLOC_ADD(&alloclen, alloclen, 1);
driver = git__calloc(1, alloclen);
GIT_ERROR_CHECK_ALLOC(driver);
memcpy(driver->name, name, namelen);
*out = driver;
if (namelen_out)
*namelen_out = namelen;
return 0;
}
static int git_diff_driver_builtin(
git_diff_driver **out,
git_diff_driver_registry *reg,
const char *driver_name)
{
git_diff_driver_definition *ddef = NULL;
git_diff_driver *drv = NULL;
int error = 0;
size_t idx;
for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) {
if (!strcasecmp(driver_name, builtin_defs[idx].name)) {
ddef = &builtin_defs[idx];
break;
}
}
if (!ddef)
goto done;
if ((error = diff_driver_alloc(&drv, NULL, ddef->name)) < 0)
goto done;
drv->type = DIFF_DRIVER_PATTERNLIST;
if (ddef->fns &&
(error = diff_driver_add_patterns(
drv, ddef->fns, ddef->flags)) < 0)
goto done;
if (ddef->words &&
(error = git_regexp_compile(&drv->word_pattern, ddef->words, ddef->flags)) < 0)
goto done;
if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
goto done;
done:
if (error && drv)
git_diff_driver_free(drv);
else
*out = drv;
return error;
}
static int git_diff_driver_load(
git_diff_driver **out, git_repository *repo, const char *driver_name)
{
int error = 0;
git_diff_driver_registry *reg;
git_diff_driver *drv;
size_t namelen;
git_config *cfg = NULL;
git_buf name = GIT_BUF_INIT;
git_config_entry *ce = NULL;
bool found_driver = false;
if ((reg = git_repository_driver_registry(repo)) == NULL)
return -1;
if ((drv = git_strmap_get(reg->drivers, driver_name)) != NULL) {
*out = drv;
return 0;
}
if ((error = diff_driver_alloc(&drv, &namelen, driver_name)) < 0)
goto done;
drv->type = DIFF_DRIVER_AUTO;
/* if you can't read config for repo, just use default driver */
if (git_repository_config_snapshot(&cfg, repo) < 0) {
git_error_clear();
goto done;
}
if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0)
goto done;
switch (git_config__get_bool_force(cfg, name.ptr, -1)) {
case true:
/* if diff.<driver>.binary is true, just return the binary driver */
*out = &global_drivers[DIFF_DRIVER_BINARY];
goto done;
case false:
/* if diff.<driver>.binary is false, force binary checks off */
/* but still may have custom function context patterns, etc. */
drv->binary_flags = GIT_DIFF_FORCE_TEXT;
found_driver = true;
break;
default:
/* diff.<driver>.binary unspecified or "auto", so just continue */
break;
}
/* TODO: warn if diff.<name>.command or diff.<name>.textconv are set */
git_buf_truncate(&name, namelen + strlen("diff.."));
if ((error = git_buf_PUTS(&name, "xfuncname")) < 0)
goto done;
if ((error = git_config_get_multivar_foreach(
cfg, name.ptr, NULL, diff_driver_xfuncname, drv)) < 0) {
if (error != GIT_ENOTFOUND)
goto done;
git_error_clear(); /* no diff.<driver>.xfuncname, so just continue */
}
git_buf_truncate(&name, namelen + strlen("diff.."));
if ((error = git_buf_PUTS(&name, "funcname")) < 0)
goto done;
if ((error = git_config_get_multivar_foreach(
cfg, name.ptr, NULL, diff_driver_funcname, drv)) < 0) {
if (error != GIT_ENOTFOUND)
goto done;
git_error_clear(); /* no diff.<driver>.funcname, so just continue */
}
/* if we found any patterns, set driver type to use correct callback */
if (git_array_size(drv->fn_patterns) > 0) {
drv->type = DIFF_DRIVER_PATTERNLIST;
found_driver = true;
}
git_buf_truncate(&name, namelen + strlen("diff.."));
if ((error = git_buf_PUTS(&name, "wordregex")) < 0)
goto done;
if ((error = git_config__lookup_entry(&ce, cfg, name.ptr, false)) < 0)
goto done;
if (!ce || !ce->value)
/* no diff.<driver>.wordregex, so just continue */;
else if (!(error = git_regexp_compile(&drv->word_pattern, ce->value, 0)))
found_driver = true;
else {
/* TODO: warn about bad regex instead of failure */
goto done;
}
/* TODO: look up diff.<driver>.algorithm to turn on minimal / patience
* diff in drv->other_flags
*/
/* if no driver config found at all, fall back on AUTO driver */
if (!found_driver)
goto done;
/* store driver in registry */
if ((error = git_strmap_set(reg->drivers, drv->name, drv)) < 0)
goto done;
*out = drv;
done:
git_config_entry_free(ce);
git_buf_dispose(&name);
git_config_free(cfg);
if (!*out) {
int error2 = git_diff_driver_builtin(out, reg, driver_name);
if (!error)
error = error2;
}
if (drv && drv != *out)
git_diff_driver_free(drv);
return error;
}
int git_diff_driver_lookup(
git_diff_driver **out, git_repository *repo,
git_attr_session *attrsession, const char *path)
{
int error = 0;
const char *values[1], *attrs[] = { "diff" };
assert(out);
*out = NULL;
if (!repo || !path || !strlen(path))
/* just use the auto value */;
else if ((error = git_attr_get_many_with_session(values, repo,
attrsession, 0, path, 1, attrs)) < 0)
/* return error below */;
else if (GIT_ATTR_IS_UNSPECIFIED(values[0]))
/* just use the auto value */;
else if (GIT_ATTR_IS_FALSE(values[0]))
*out = &global_drivers[DIFF_DRIVER_BINARY];
else if (GIT_ATTR_IS_TRUE(values[0]))
*out = &global_drivers[DIFF_DRIVER_TEXT];
/* otherwise look for driver information in config and build driver */
else if ((error = git_diff_driver_load(out, repo, values[0])) < 0) {
if (error == GIT_ENOTFOUND) {
error = 0;
git_error_clear();
}
}
if (!*out)
*out = &global_drivers[DIFF_DRIVER_AUTO];
return error;
}
void git_diff_driver_free(git_diff_driver *driver)
{
size_t i;
if (!driver)
return;
for (i = 0; i < git_array_size(driver->fn_patterns); ++i)
git_regexp_dispose(& git_array_get(driver->fn_patterns, i)->re);
git_array_clear(driver->fn_patterns);
git_regexp_dispose(&driver->word_pattern);
git__free(driver);
}
void git_diff_driver_update_options(
uint32_t *option_flags, git_diff_driver *driver)
{
if ((*option_flags & FORCE_DIFFABLE) == 0)
*option_flags |= driver->binary_flags;
*option_flags |= driver->other_flags;
}
int git_diff_driver_content_is_binary(
git_diff_driver *driver, const char *content, size_t content_len)
{
git_buf search = GIT_BUF_INIT;
GIT_UNUSED(driver);
git_buf_attach_notowned(&search, content,
min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL));
/* TODO: provide encoding / binary detection callbacks that can
* be UTF-8 aware, etc. For now, instead of trying to be smart,
* let's just use the simple NUL-byte detection that core git uses.
*/
/* previously was: if (git_buf_text_is_binary(&search)) */
if (git_buf_text_contains_nul(&search))
return 1;
return 0;
}
static int diff_context_line__simple(
git_diff_driver *driver, git_buf *line)
{
char firstch = line->ptr[0];
GIT_UNUSED(driver);
return (git__isalpha(firstch) || firstch == '_' || firstch == '$');
}
static int diff_context_line__pattern_match(
git_diff_driver *driver, git_buf *line)
{
size_t i, maxi = git_array_size(driver->fn_patterns);
git_regmatch pmatch[2];
for (i = 0; i < maxi; ++i) {
git_diff_driver_pattern *pat = git_array_get(driver->fn_patterns, i);
if (!git_regexp_search(&pat->re, line->ptr, 2, pmatch)) {
if (pat->flags & REG_NEGATE)
return false;
/* use pmatch data to trim line data */
i = (pmatch[1].start >= 0) ? 1 : 0;
git_buf_consume(line, git_buf_cstr(line) + pmatch[i].start);
git_buf_truncate(line, pmatch[i].end - pmatch[i].start);
git_buf_rtrim(line);
return true;
}
}
return false;
}
static long diff_context_find(
const char *line,
long line_len,
char *out,
long out_size,
void *payload)
{
git_diff_find_context_payload *ctxt = payload;
if (git_buf_set(&ctxt->line, line, (size_t)line_len) < 0)
return -1;
git_buf_rtrim(&ctxt->line);
if (!ctxt->line.size)
return -1;
if (!ctxt->match_line || !ctxt->match_line(ctxt->driver, &ctxt->line))
return -1;
if (out_size > (long)ctxt->line.size)
out_size = (long)ctxt->line.size;
memcpy(out, ctxt->line.ptr, (size_t)out_size);
return out_size;
}
void git_diff_find_context_init(
git_diff_find_context_fn *findfn_out,
git_diff_find_context_payload *payload_out,
git_diff_driver *driver)
{
*findfn_out = driver ? diff_context_find : NULL;
memset(payload_out, 0, sizeof(*payload_out));
if (driver) {
payload_out->driver = driver;
payload_out->match_line = (driver->type == DIFF_DRIVER_PATTERNLIST) ?
diff_context_line__pattern_match : diff_context_line__simple;
git_buf_init(&payload_out->line, 0);
}
}
void git_diff_find_context_clear(git_diff_find_context_payload *payload)
{
if (payload) {
git_buf_dispose(&payload->line);
payload->driver = NULL;
}
}
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_driver_h__
#define INCLUDE_diff_driver_h__
#include "common.h"
#include "attr_file.h"
#include "buffer.h"
typedef struct git_diff_driver_registry git_diff_driver_registry;
git_diff_driver_registry *git_diff_driver_registry_new(void);
void git_diff_driver_registry_free(git_diff_driver_registry *);
typedef struct git_diff_driver git_diff_driver;
int git_diff_driver_lookup(git_diff_driver **, git_repository *,
git_attr_session *attrsession, const char *);
void git_diff_driver_free(git_diff_driver *);
/* diff option flags to force off and on for this driver */
void git_diff_driver_update_options(uint32_t *option_flags, git_diff_driver *);
/* returns -1 meaning "unknown", 0 meaning not binary, 1 meaning binary */
int git_diff_driver_content_is_binary(
git_diff_driver *, const char *content, size_t content_len);
typedef long (*git_diff_find_context_fn)(
const char *, long, char *, long, void *);
typedef int (*git_diff_find_context_line)(
git_diff_driver *, git_buf *);
typedef struct {
git_diff_driver *driver;
git_diff_find_context_line match_line;
git_buf line;
} git_diff_find_context_payload;
void git_diff_find_context_init(
git_diff_find_context_fn *findfn_out,
git_diff_find_context_payload *payload_out,
git_diff_driver *driver);
void git_diff_find_context_clear(git_diff_find_context_payload *);
#endif
+474
View File
@@ -0,0 +1,474 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "diff_file.h"
#include "git2/blob.h"
#include "git2/submodule.h"
#include "diff.h"
#include "diff_generate.h"
#include "odb.h"
#include "futils.h"
#include "filter.h"
#define DIFF_MAX_FILESIZE 0x20000000
static bool diff_file_content_binary_by_size(git_diff_file_content *fc)
{
/* if we have diff opts, check max_size vs file size */
if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) == 0 &&
fc->opts_max_size > 0 &&
fc->file->size > fc->opts_max_size)
fc->file->flags |= GIT_DIFF_FLAG_BINARY;
return ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0);
}
static void diff_file_content_binary_by_content(git_diff_file_content *fc)
{
if ((fc->file->flags & DIFF_FLAGS_KNOWN_BINARY) != 0)
return;
switch (git_diff_driver_content_is_binary(
fc->driver, fc->map.data, fc->map.len)) {
case 0: fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY; break;
case 1: fc->file->flags |= GIT_DIFF_FLAG_BINARY; break;
default: break;
}
}
static int diff_file_content_init_common(
git_diff_file_content *fc, const git_diff_options *opts)
{
fc->opts_flags = opts ? opts->flags : GIT_DIFF_NORMAL;
if (opts && opts->max_size >= 0)
fc->opts_max_size = opts->max_size ?
opts->max_size : DIFF_MAX_FILESIZE;
if (fc->src == GIT_ITERATOR_TYPE_EMPTY)
fc->src = GIT_ITERATOR_TYPE_TREE;
if (!fc->driver &&
git_diff_driver_lookup(&fc->driver, fc->repo,
NULL, fc->file->path) < 0)
return -1;
/* give driver a chance to modify options */
git_diff_driver_update_options(&fc->opts_flags, fc->driver);
/* make sure file is conceivable mmap-able */
if ((size_t)fc->file->size != fc->file->size)
fc->file->flags |= GIT_DIFF_FLAG_BINARY;
/* check if user is forcing text diff the file */
else if (fc->opts_flags & GIT_DIFF_FORCE_TEXT) {
fc->file->flags &= ~GIT_DIFF_FLAG_BINARY;
fc->file->flags |= GIT_DIFF_FLAG_NOT_BINARY;
}
/* check if user is forcing binary diff the file */
else if (fc->opts_flags & GIT_DIFF_FORCE_BINARY) {
fc->file->flags &= ~GIT_DIFF_FLAG_NOT_BINARY;
fc->file->flags |= GIT_DIFF_FLAG_BINARY;
}
diff_file_content_binary_by_size(fc);
if ((fc->flags & GIT_DIFF_FLAG__NO_DATA) != 0) {
fc->flags |= GIT_DIFF_FLAG__LOADED;
fc->map.len = 0;
fc->map.data = "";
}
if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
diff_file_content_binary_by_content(fc);
return 0;
}
int git_diff_file_content__init_from_diff(
git_diff_file_content *fc,
git_diff *diff,
git_diff_delta *delta,
bool use_old)
{
bool has_data = true;
memset(fc, 0, sizeof(*fc));
fc->repo = diff->repo;
fc->file = use_old ? &delta->old_file : &delta->new_file;
fc->src = use_old ? diff->old_src : diff->new_src;
if (git_diff_driver_lookup(&fc->driver, fc->repo,
&diff->attrsession, fc->file->path) < 0)
return -1;
switch (delta->status) {
case GIT_DELTA_ADDED:
has_data = !use_old; break;
case GIT_DELTA_DELETED:
has_data = use_old; break;
case GIT_DELTA_UNTRACKED:
has_data = !use_old &&
(diff->opts.flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) != 0;
break;
case GIT_DELTA_UNREADABLE:
case GIT_DELTA_MODIFIED:
case GIT_DELTA_COPIED:
case GIT_DELTA_RENAMED:
break;
default:
has_data = false;
break;
}
if (!has_data)
fc->flags |= GIT_DIFF_FLAG__NO_DATA;
return diff_file_content_init_common(fc, &diff->opts);
}
int git_diff_file_content__init_from_src(
git_diff_file_content *fc,
git_repository *repo,
const git_diff_options *opts,
const git_diff_file_content_src *src,
git_diff_file *as_file)
{
memset(fc, 0, sizeof(*fc));
fc->repo = repo;
fc->file = as_file;
if (!src->blob && !src->buf) {
fc->flags |= GIT_DIFF_FLAG__NO_DATA;
} else {
fc->flags |= GIT_DIFF_FLAG__LOADED;
fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
fc->file->mode = GIT_FILEMODE_BLOB;
if (src->blob) {
git_blob_dup((git_blob **)&fc->blob, (git_blob *) src->blob);
fc->file->size = git_blob_rawsize(src->blob);
git_oid_cpy(&fc->file->id, git_blob_id(src->blob));
fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = (size_t)fc->file->size;
fc->map.data = (char *)git_blob_rawcontent(src->blob);
fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
} else {
fc->file->size = src->buflen;
git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJECT_BLOB);
fc->file->id_abbrev = GIT_OID_HEXSZ;
fc->map.len = src->buflen;
fc->map.data = (char *)src->buf;
}
}
return diff_file_content_init_common(fc, opts);
}
static int diff_file_content_commit_to_str(
git_diff_file_content *fc, bool check_status)
{
char oid[GIT_OID_HEXSZ+1];
git_buf content = GIT_BUF_INIT;
const char *status = "";
if (check_status) {
int error = 0;
git_submodule *sm = NULL;
unsigned int sm_status = 0;
const git_oid *sm_head;
if ((error = git_submodule_lookup(&sm, fc->repo, fc->file->path)) < 0) {
/* GIT_EEXISTS means a "submodule" that has not been git added */
if (error == GIT_EEXISTS) {
git_error_clear();
error = 0;
}
return error;
}
if ((error = git_submodule_status(&sm_status, fc->repo, fc->file->path, GIT_SUBMODULE_IGNORE_UNSPECIFIED)) < 0) {
git_submodule_free(sm);
return error;
}
/* update OID if we didn't have it previously */
if ((fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0 &&
((sm_head = git_submodule_wd_id(sm)) != NULL ||
(sm_head = git_submodule_head_id(sm)) != NULL))
{
git_oid_cpy(&fc->file->id, sm_head);
fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
}
if (GIT_SUBMODULE_STATUS_IS_WD_DIRTY(sm_status))
status = "-dirty";
git_submodule_free(sm);
}
git_oid_tostr(oid, sizeof(oid), &fc->file->id);
if (git_buf_printf(&content, "Subproject commit %s%s\n", oid, status) < 0)
return -1;
fc->map.len = git_buf_len(&content);
fc->map.data = git_buf_detach(&content);
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
return 0;
}
static int diff_file_content_load_blob(
git_diff_file_content *fc,
git_diff_options *opts)
{
int error = 0;
git_odb_object *odb_obj = NULL;
if (git_oid_is_zero(&fc->file->id))
return 0;
if (fc->file->mode == GIT_FILEMODE_COMMIT)
return diff_file_content_commit_to_str(fc, false);
/* if we don't know size, try to peek at object header first */
if (!fc->file->size) {
if ((error = git_diff_file__resolve_zero_size(
fc->file, &odb_obj, fc->repo)) < 0)
return error;
}
if ((opts->flags & GIT_DIFF_SHOW_BINARY) == 0 &&
diff_file_content_binary_by_size(fc))
return 0;
if (odb_obj != NULL) {
error = git_object__from_odb_object(
(git_object **)&fc->blob, fc->repo, odb_obj, GIT_OBJECT_BLOB);
git_odb_object_free(odb_obj);
} else {
error = git_blob_lookup(
(git_blob **)&fc->blob, fc->repo, &fc->file->id);
}
if (!error) {
fc->flags |= GIT_DIFF_FLAG__FREE_BLOB;
fc->map.data = (void *)git_blob_rawcontent(fc->blob);
fc->map.len = (size_t)git_blob_rawsize(fc->blob);
}
return error;
}
static int diff_file_content_load_workdir_symlink_fake(
git_diff_file_content *fc, git_buf *path)
{
git_buf target = GIT_BUF_INIT;
int error;
if ((error = git_futils_readbuffer(&target, path->ptr)) < 0)
return error;
fc->map.len = git_buf_len(&target);
fc->map.data = git_buf_detach(&target);
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
git_buf_dispose(&target);
return error;
}
static int diff_file_content_load_workdir_symlink(
git_diff_file_content *fc, git_buf *path)
{
ssize_t alloc_len, read_len;
int symlink_supported, error;
if ((error = git_repository__configmap_lookup(
&symlink_supported, fc->repo, GIT_CONFIGMAP_SYMLINKS)) < 0)
return -1;
if (!symlink_supported)
return diff_file_content_load_workdir_symlink_fake(fc, path);
/* link path on disk could be UTF-16, so prepare a buffer that is
* big enough to handle some UTF-8 data expansion
*/
alloc_len = (ssize_t)(fc->file->size * 2) + 1;
fc->map.data = git__calloc(alloc_len, sizeof(char));
GIT_ERROR_CHECK_ALLOC(fc->map.data);
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
read_len = p_readlink(git_buf_cstr(path), fc->map.data, alloc_len);
if (read_len < 0) {
git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", fc->file->path);
return -1;
}
fc->map.len = read_len;
return 0;
}
static int diff_file_content_load_workdir_file(
git_diff_file_content *fc,
git_buf *path,
git_diff_options *diff_opts)
{
int error = 0;
git_filter_list *fl = NULL;
git_file fd = git_futils_open_ro(git_buf_cstr(path));
git_buf raw = GIT_BUF_INIT;
if (fd < 0)
return fd;
if (!fc->file->size)
error = git_futils_filesize(&fc->file->size, fd);
if (error < 0 || !fc->file->size)
goto cleanup;
if ((diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0 &&
diff_file_content_binary_by_size(fc))
goto cleanup;
if ((error = git_filter_list_load(
&fl, fc->repo, NULL, fc->file->path,
GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE)) < 0)
goto cleanup;
/* if there are no filters, try to mmap the file */
if (fl == NULL) {
if (!(error = git_futils_mmap_ro(
&fc->map, fd, 0, (size_t)fc->file->size))) {
fc->flags |= GIT_DIFF_FLAG__UNMAP_DATA;
goto cleanup;
}
/* if mmap failed, fall through to try readbuffer below */
git_error_clear();
}
if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)fc->file->size))) {
git_buf out = GIT_BUF_INIT;
error = git_filter_list_apply_to_data(&out, fl, &raw);
if (out.ptr != raw.ptr)
git_buf_dispose(&raw);
if (!error) {
fc->map.len = out.size;
fc->map.data = out.ptr;
fc->flags |= GIT_DIFF_FLAG__FREE_DATA;
}
}
cleanup:
git_filter_list_free(fl);
p_close(fd);
return error;
}
static int diff_file_content_load_workdir(
git_diff_file_content *fc,
git_diff_options *diff_opts)
{
int error = 0;
git_buf path = GIT_BUF_INIT;
if (fc->file->mode == GIT_FILEMODE_COMMIT)
return diff_file_content_commit_to_str(fc, true);
if (fc->file->mode == GIT_FILEMODE_TREE)
return 0;
if (git_buf_joinpath(
&path, git_repository_workdir(fc->repo), fc->file->path) < 0)
return -1;
if (S_ISLNK(fc->file->mode))
error = diff_file_content_load_workdir_symlink(fc, &path);
else
error = diff_file_content_load_workdir_file(fc, &path, diff_opts);
/* once data is loaded, update OID if we didn't have it previously */
if (!error && (fc->file->flags & GIT_DIFF_FLAG_VALID_ID) == 0) {
error = git_odb_hash(
&fc->file->id, fc->map.data, fc->map.len, GIT_OBJECT_BLOB);
fc->file->flags |= GIT_DIFF_FLAG_VALID_ID;
}
git_buf_dispose(&path);
return error;
}
int git_diff_file_content__load(
git_diff_file_content *fc,
git_diff_options *diff_opts)
{
int error = 0;
if ((fc->flags & GIT_DIFF_FLAG__LOADED) != 0)
return 0;
if ((fc->file->flags & GIT_DIFF_FLAG_BINARY) != 0 &&
(diff_opts->flags & GIT_DIFF_SHOW_BINARY) == 0)
return 0;
if (fc->src == GIT_ITERATOR_TYPE_WORKDIR)
error = diff_file_content_load_workdir(fc, diff_opts);
else
error = diff_file_content_load_blob(fc, diff_opts);
if (error)
return error;
fc->flags |= GIT_DIFF_FLAG__LOADED;
diff_file_content_binary_by_content(fc);
return 0;
}
void git_diff_file_content__unload(git_diff_file_content *fc)
{
if ((fc->flags & GIT_DIFF_FLAG__LOADED) == 0)
return;
if (fc->flags & GIT_DIFF_FLAG__FREE_DATA) {
git__free(fc->map.data);
fc->map.data = "";
fc->map.len = 0;
fc->flags &= ~GIT_DIFF_FLAG__FREE_DATA;
}
else if (fc->flags & GIT_DIFF_FLAG__UNMAP_DATA) {
git_futils_mmap_free(&fc->map);
fc->map.data = "";
fc->map.len = 0;
fc->flags &= ~GIT_DIFF_FLAG__UNMAP_DATA;
}
if (fc->flags & GIT_DIFF_FLAG__FREE_BLOB) {
git_blob_free((git_blob *)fc->blob);
fc->blob = NULL;
fc->flags &= ~GIT_DIFF_FLAG__FREE_BLOB;
}
fc->flags &= ~GIT_DIFF_FLAG__LOADED;
}
void git_diff_file_content__clear(git_diff_file_content *fc)
{
git_diff_file_content__unload(fc);
/* for now, nothing else to do */
}
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_file_h__
#define INCLUDE_diff_file_h__
#include "common.h"
#include "diff.h"
#include "diff_driver.h"
#include "map.h"
/* expanded information for one side of a delta */
typedef struct {
git_repository *repo;
git_diff_file *file;
git_diff_driver *driver;
uint32_t flags;
uint32_t opts_flags;
git_object_size_t opts_max_size;
git_iterator_type_t src;
const git_blob *blob;
git_map map;
} git_diff_file_content;
extern int git_diff_file_content__init_from_diff(
git_diff_file_content *fc,
git_diff *diff,
git_diff_delta *delta,
bool use_old);
typedef struct {
const git_blob *blob;
const void *buf;
size_t buflen;
const char *as_path;
} git_diff_file_content_src;
#define GIT_DIFF_FILE_CONTENT_SRC__BLOB(BLOB,PATH) { (BLOB),NULL,0,(PATH) }
#define GIT_DIFF_FILE_CONTENT_SRC__BUF(BUF,LEN,PATH) { NULL,(BUF),(LEN),(PATH) }
extern int git_diff_file_content__init_from_src(
git_diff_file_content *fc,
git_repository *repo,
const git_diff_options *opts,
const git_diff_file_content_src *src,
git_diff_file *as_file);
/* this loads the blob/file-on-disk as needed */
extern int git_diff_file_content__load(
git_diff_file_content *fc,
git_diff_options *diff_opts);
/* this releases the blob/file-in-memory */
extern void git_diff_file_content__unload(git_diff_file_content *fc);
/* this unloads and also releases any other resources */
extern void git_diff_file_content__clear(git_diff_file_content *fc);
#endif
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_generate_h__
#define INCLUDE_diff_generate_h__
#include "common.h"
#include "diff.h"
#include "pool.h"
#include "index.h"
enum {
GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */
GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */
GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */
GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */
GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */
};
#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY)
#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA)
enum {
GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */
GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */
GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */
GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */
GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */
GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */
GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */
GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */
GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18),
GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19),
GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20),
};
#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF)
#define GIT_DIFF__VERBOSE (1 << 30)
extern void git_diff_addref(git_diff *diff);
extern bool git_diff_delta__should_skip(
const git_diff_options *opts, const git_diff_delta *delta);
extern int git_diff__from_iterators(
git_diff **diff_ptr,
git_repository *repo,
git_iterator *old_iter,
git_iterator *new_iter,
const git_diff_options *opts);
extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
extern int git_diff__paired_foreach(
git_diff *idx2head,
git_diff *wd2idx,
int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload),
void *payload);
/* Merge two `git_diff`s according to the callback given by `cb`. */
typedef git_diff_delta *(*git_diff__merge_cb)(
const git_diff_delta *left,
const git_diff_delta *right,
git_pool *pool);
extern int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
extern git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool);
/* Duplicate a `git_diff_delta` out of the `git_pool` */
extern git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool);
extern int git_diff__oid_for_file(
git_oid *out,
git_diff *diff,
const char *path,
uint16_t mode,
git_object_size_t size);
extern int git_diff__oid_for_entry(
git_oid *out,
git_diff *diff,
const git_index_entry *src,
uint16_t mode,
const git_oid *update_match);
/*
* Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is
* not possible, then it will return the git_odb_object that had to be
* loaded and the caller can use it or dispose of it as needed.
*/
GIT_INLINE(int) git_diff_file__resolve_zero_size(
git_diff_file *file, git_odb_object **odb_obj, git_repository *repo)
{
int error;
git_odb *odb;
size_t len;
git_object_t type;
if ((error = git_repository_odb(&odb, repo)) < 0)
return error;
error = git_odb__read_header_or_object(
odb_obj, &len, &type, odb, &file->id);
git_odb_free(odb);
if (!error)
file->size = (git_object_size_t)len;
return error;
}
#endif
+108
View File
@@ -0,0 +1,108 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "diff_parse.h"
#include "diff.h"
#include "patch.h"
#include "patch_parse.h"
static void diff_parsed_free(git_diff *d)
{
git_diff_parsed *diff = (git_diff_parsed *)d;
git_patch *patch;
size_t i;
git_vector_foreach(&diff->patches, i, patch)
git_patch_free(patch);
git_vector_free(&diff->patches);
git_vector_free(&diff->base.deltas);
git_pool_clear(&diff->base.pool);
git__memzero(diff, sizeof(*diff));
git__free(diff);
}
static git_diff_parsed *diff_parsed_alloc(void)
{
git_diff_parsed *diff;
if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL)
return NULL;
GIT_REFCOUNT_INC(&diff->base);
diff->base.type = GIT_DIFF_TYPE_PARSED;
diff->base.strcomp = git__strcmp;
diff->base.strncomp = git__strncmp;
diff->base.pfxcomp = git__prefixcmp;
diff->base.entrycomp = git_diff__entry_cmp;
diff->base.patch_fn = git_patch_parsed_from_diff;
diff->base.free_fn = diff_parsed_free;
if (git_diff_options_init(&diff->base.opts, GIT_DIFF_OPTIONS_VERSION) < 0) {
git__free(diff);
return NULL;
}
diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE;
git_pool_init(&diff->base.pool, 1);
if (git_vector_init(&diff->patches, 0, NULL) < 0 ||
git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) {
git_diff_free(&diff->base);
return NULL;
}
git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp);
return diff;
}
int git_diff_from_buffer(
git_diff **out,
const char *content,
size_t content_len)
{
git_diff_parsed *diff;
git_patch *patch;
git_patch_parse_ctx *ctx = NULL;
int error = 0;
*out = NULL;
diff = diff_parsed_alloc();
GIT_ERROR_CHECK_ALLOC(diff);
ctx = git_patch_parse_ctx_init(content, content_len, NULL);
GIT_ERROR_CHECK_ALLOC(ctx);
while (ctx->parse_ctx.remain_len) {
if ((error = git_patch_parse(&patch, ctx)) < 0)
break;
git_vector_insert(&diff->patches, patch);
git_vector_insert(&diff->base.deltas, patch->delta);
}
if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) {
git_error_clear();
error = 0;
}
git_patch_parse_ctx_free(ctx);
if (error < 0)
git_diff_free(&diff->base);
else
*out = &diff->base;
return error;
}
+20
View File
@@ -0,0 +1,20 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_parse_h__
#define INCLUDE_diff_parse_h__
#include "common.h"
#include "diff.h"
typedef struct {
struct git_diff base;
git_vector patches;
} git_diff_parsed;
#endif
+802
View File
@@ -0,0 +1,802 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "diff.h"
#include "diff_file.h"
#include "patch_generate.h"
#include "futils.h"
#include "zstream.h"
#include "blob.h"
#include "delta.h"
#include "git2/sys/diff.h"
typedef struct {
git_diff_format_t format;
git_diff_line_cb print_cb;
void *payload;
git_buf *buf;
git_diff_line line;
const char *old_prefix;
const char *new_prefix;
uint32_t flags;
int id_strlen;
int (*strcomp)(const char *, const char *);
} diff_print_info;
static int diff_print_info_init__common(
diff_print_info *pi,
git_buf *out,
git_repository *repo,
git_diff_format_t format,
git_diff_line_cb cb,
void *payload)
{
pi->format = format;
pi->print_cb = cb;
pi->payload = payload;
pi->buf = out;
if (!pi->id_strlen) {
if (!repo)
pi->id_strlen = GIT_ABBREV_DEFAULT;
else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0)
return -1;
}
if (pi->id_strlen > GIT_OID_HEXSZ)
pi->id_strlen = GIT_OID_HEXSZ;
memset(&pi->line, 0, sizeof(pi->line));
pi->line.old_lineno = -1;
pi->line.new_lineno = -1;
pi->line.num_lines = 1;
return 0;
}
static int diff_print_info_init_fromdiff(
diff_print_info *pi,
git_buf *out,
git_diff *diff,
git_diff_format_t format,
git_diff_line_cb cb,
void *payload)
{
git_repository *repo = diff ? diff->repo : NULL;
memset(pi, 0, sizeof(diff_print_info));
if (diff) {
pi->flags = diff->opts.flags;
pi->id_strlen = diff->opts.id_abbrev;
pi->old_prefix = diff->opts.old_prefix;
pi->new_prefix = diff->opts.new_prefix;
pi->strcomp = diff->strcomp;
}
return diff_print_info_init__common(pi, out, repo, format, cb, payload);
}
static int diff_print_info_init_frompatch(
diff_print_info *pi,
git_buf *out,
git_patch *patch,
git_diff_format_t format,
git_diff_line_cb cb,
void *payload)
{
assert(patch);
memset(pi, 0, sizeof(diff_print_info));
pi->flags = patch->diff_opts.flags;
pi->id_strlen = patch->diff_opts.id_abbrev;
pi->old_prefix = patch->diff_opts.old_prefix;
pi->new_prefix = patch->diff_opts.new_prefix;
return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
}
static char diff_pick_suffix(int mode)
{
if (S_ISDIR(mode))
return '/';
else if (GIT_PERMS_IS_EXEC(mode)) /* -V536 */
/* in git, modes are very regular, so we must have 0100755 mode */
return '*';
else
return ' ';
}
char git_diff_status_char(git_delta_t status)
{
char code;
switch (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;
case GIT_DELTA_TYPECHANGE: code = 'T'; break;
case GIT_DELTA_UNREADABLE: code = 'X'; break;
default: code = ' '; break;
}
return code;
}
static int diff_print_one_name_only(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
git_buf *out = pi->buf;
GIT_UNUSED(progress);
if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 &&
delta->status == GIT_DELTA_UNMODIFIED)
return 0;
git_buf_clear(out);
git_buf_puts(out, delta->new_file.path);
git_buf_putc(out, '\n');
if (git_buf_oom(out))
return -1;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
pi->line.content = git_buf_cstr(out);
pi->line.content_len = git_buf_len(out);
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_one_name_status(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
git_buf *out = pi->buf;
char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
int(*strcomp)(const char *, const char *) = pi->strcomp ?
pi->strcomp : git__strcmp;
GIT_UNUSED(progress);
if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
return 0;
old_suffix = diff_pick_suffix(delta->old_file.mode);
new_suffix = diff_pick_suffix(delta->new_file.mode);
git_buf_clear(out);
if (delta->old_file.path != delta->new_file.path &&
strcomp(delta->old_file.path,delta->new_file.path) != 0)
git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (delta->old_file.mode != delta->new_file.mode &&
delta->old_file.mode != 0 && delta->new_file.mode != 0)
git_buf_printf(out, "%c\t%s%c %s%c\n", code,
delta->old_file.path, old_suffix, delta->new_file.path, new_suffix);
else if (old_suffix != ' ')
git_buf_printf(out, "%c\t%s%c\n", code, delta->old_file.path, old_suffix);
else
git_buf_printf(out, "%c\t%s\n", code, delta->old_file.path);
if (git_buf_oom(out))
return -1;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
pi->line.content = git_buf_cstr(out);
pi->line.content_len = git_buf_len(out);
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_one_raw(
const git_diff_delta *delta, float progress, void *data)
{
diff_print_info *pi = data;
git_buf *out = pi->buf;
int id_abbrev;
char code = git_diff_status_char(delta->status);
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
GIT_UNUSED(progress);
if ((pi->flags & GIT_DIFF_SHOW_UNMODIFIED) == 0 && code == ' ')
return 0;
git_buf_clear(out);
id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
delta->new_file.id_abbrev;
if (pi->id_strlen > id_abbrev) {
git_error_set(GIT_ERROR_PATCH,
"the patch input contains %d id characters (cannot print %d)",
id_abbrev, pi->id_strlen);
return -1;
}
git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
git_buf_printf(
out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
if (delta->similarity > 0)
git_buf_printf(out, "%03u", delta->similarity);
if (delta->old_file.path != delta->new_file.path)
git_buf_printf(
out, "\t%s %s\n", delta->old_file.path, delta->new_file.path);
else
git_buf_printf(
out, "\t%s\n", delta->old_file.path ?
delta->old_file.path : delta->new_file.path);
if (git_buf_oom(out))
return -1;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
pi->line.content = git_buf_cstr(out);
pi->line.content_len = git_buf_len(out);
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_modes(
git_buf *out, const git_diff_delta *delta)
{
git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
return git_buf_oom(out) ? -1 : 0;
}
static int diff_print_oid_range(
git_buf *out, const git_diff_delta *delta, int id_strlen,
bool print_index)
{
char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
if (delta->old_file.mode &&
id_strlen > delta->old_file.id_abbrev) {
git_error_set(GIT_ERROR_PATCH,
"the patch input contains %d id characters (cannot print %d)",
delta->old_file.id_abbrev, id_strlen);
return -1;
}
if ((delta->new_file.mode &&
id_strlen > delta->new_file.id_abbrev)) {
git_error_set(GIT_ERROR_PATCH,
"the patch input contains %d id characters (cannot print %d)",
delta->new_file.id_abbrev, id_strlen);
return -1;
}
git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
if (delta->old_file.mode == delta->new_file.mode) {
if (print_index)
git_buf_printf(out, "index %s..%s %o\n",
start_oid, end_oid, delta->old_file.mode);
} else {
if (delta->old_file.mode == 0)
git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
else if (delta->new_file.mode == 0)
git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
else
diff_print_modes(out, delta);
if (print_index)
git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
}
return git_buf_oom(out) ? -1 : 0;
}
static int diff_delta_format_path(
git_buf *out, const char *prefix, const char *filename)
{
if (git_buf_joinpath(out, prefix, filename) < 0)
return -1;
return git_buf_quote(out);
}
static int diff_delta_format_with_paths(
git_buf *out,
const git_diff_delta *delta,
const char *template,
const char *oldpath,
const char *newpath)
{
if (git_oid_is_zero(&delta->old_file.id))
oldpath = "/dev/null";
if (git_oid_is_zero(&delta->new_file.id))
newpath = "/dev/null";
return git_buf_printf(out, template, oldpath, newpath);
}
int diff_delta_format_similarity_header(
git_buf *out,
const git_diff_delta *delta)
{
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
const char *type;
int error = 0;
if (delta->similarity > 100) {
git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity);
error = -1;
goto done;
}
if (delta->status == GIT_DELTA_RENAMED)
type = "rename";
else if (delta->status == GIT_DELTA_COPIED)
type = "copy";
else
abort();
if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
(error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
(error = git_buf_quote(&old_path)) < 0 ||
(error = git_buf_quote(&new_path)) < 0)
goto done;
git_buf_printf(out,
"similarity index %d%%\n"
"%s from %s\n"
"%s to %s\n",
delta->similarity,
type, old_path.ptr,
type, new_path.ptr);
if (git_buf_oom(out))
error = -1;
done:
git_buf_dispose(&old_path);
git_buf_dispose(&new_path);
return error;
}
static bool delta_is_unchanged(const git_diff_delta *delta)
{
if (git_oid_is_zero(&delta->old_file.id) &&
git_oid_is_zero(&delta->new_file.id))
return true;
if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
delta->new_file.mode == GIT_FILEMODE_COMMIT)
return false;
if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
return true;
return false;
}
int git_diff_delta__format_file_header(
git_buf *out,
const git_diff_delta *delta,
const char *oldpfx,
const char *newpfx,
int id_strlen,
bool print_index)
{
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
bool unchanged = delta_is_unchanged(delta);
int error = 0;
if (!oldpfx)
oldpfx = DIFF_OLD_PREFIX_DEFAULT;
if (!newpfx)
newpfx = DIFF_NEW_PREFIX_DEFAULT;
if (!id_strlen)
id_strlen = GIT_ABBREV_DEFAULT;
if ((error = diff_delta_format_path(
&old_path, oldpfx, delta->old_file.path)) < 0 ||
(error = diff_delta_format_path(
&new_path, newpfx, delta->new_file.path)) < 0)
goto done;
git_buf_clear(out);
git_buf_printf(out, "diff --git %s %s\n",
old_path.ptr, new_path.ptr);
if (delta->status == GIT_DELTA_RENAMED ||
(delta->status == GIT_DELTA_COPIED && unchanged)) {
if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
goto done;
}
if (!unchanged) {
if ((error = diff_print_oid_range(out, delta,
id_strlen, print_index)) < 0)
goto done;
if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
diff_delta_format_with_paths(out, delta,
"--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
}
if (unchanged && delta->old_file.mode != delta->new_file.mode)
diff_print_modes(out, delta);
if (git_buf_oom(out))
error = -1;
done:
git_buf_dispose(&old_path);
git_buf_dispose(&new_path);
return error;
}
static int format_binary(
diff_print_info *pi,
git_diff_binary_t type,
const char *data,
size_t datalen,
size_t inflatedlen)
{
const char *typename = type == GIT_DIFF_BINARY_DELTA ?
"delta" : "literal";
const char *scan, *end;
git_buf_printf(pi->buf, "%s %" PRIuZ "\n", typename, inflatedlen);
pi->line.num_lines++;
for (scan = data, end = data + datalen; scan < end; ) {
size_t chunk_len = end - scan;
if (chunk_len > 52)
chunk_len = 52;
if (chunk_len <= 26)
git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1);
else
git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1);
git_buf_encode_base85(pi->buf, scan, chunk_len);
git_buf_putc(pi->buf, '\n');
if (git_buf_oom(pi->buf))
return -1;
scan += chunk_len;
pi->line.num_lines++;
}
git_buf_putc(pi->buf, '\n');
return 0;
}
static int diff_print_patch_file_binary_noshow(
diff_print_info *pi, git_diff_delta *delta,
const char *old_pfx, const char *new_pfx)
{
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
int error;
if ((error = diff_delta_format_path(
&old_path, old_pfx, delta->old_file.path)) < 0 ||
(error = diff_delta_format_path(
&new_path, new_pfx, delta->new_file.path)) < 0)
goto done;
pi->line.num_lines = 1;
error = diff_delta_format_with_paths(
pi->buf, delta, "Binary files %s and %s differ\n",
old_path.ptr, new_path.ptr);
done:
git_buf_dispose(&old_path);
git_buf_dispose(&new_path);
return error;
}
static int diff_print_patch_file_binary(
diff_print_info *pi, git_diff_delta *delta,
const char *old_pfx, const char *new_pfx,
const git_diff_binary *binary)
{
size_t pre_binary_size;
int error;
if (delta->status == GIT_DELTA_UNMODIFIED)
return 0;
if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data)
return diff_print_patch_file_binary_noshow(
pi, delta, old_pfx, new_pfx);
pre_binary_size = pi->buf->size;
git_buf_printf(pi->buf, "GIT binary patch\n");
pi->line.num_lines++;
if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data,
binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
(error = format_binary(pi, binary->old_file.type, binary->old_file.data,
binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {
if (error == GIT_EBUFS) {
git_error_clear();
git_buf_truncate(pi->buf, pre_binary_size);
return diff_print_patch_file_binary_noshow(
pi, delta, old_pfx, new_pfx);
}
}
pi->line.num_lines++;
return error;
}
static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
int error;
diff_print_info *pi = data;
const char *oldpfx =
pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *newpfx =
pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
(pi->flags & GIT_DIFF_FORCE_BINARY);
bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
int id_strlen = pi->id_strlen;
bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID);
if (binary && show_binary)
id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev :
delta->new_file.id_abbrev;
GIT_UNUSED(progress);
if (S_ISDIR(delta->new_file.mode) ||
delta->status == GIT_DELTA_UNMODIFIED ||
delta->status == GIT_DELTA_IGNORED ||
delta->status == GIT_DELTA_UNREADABLE ||
(delta->status == GIT_DELTA_UNTRACKED &&
(pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
return 0;
if ((error = git_diff_delta__format_file_header(
pi->buf, delta, oldpfx, newpfx,
id_strlen, print_index)) < 0)
return error;
pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
pi->line.content = git_buf_cstr(pi->buf);
pi->line.content_len = git_buf_len(pi->buf);
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_patch_binary(
const git_diff_delta *delta,
const git_diff_binary *binary,
void *data)
{
diff_print_info *pi = data;
const char *old_pfx =
pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
const char *new_pfx =
pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
int error;
git_buf_clear(pi->buf);
if ((error = diff_print_patch_file_binary(
pi, (git_diff_delta *)delta, old_pfx, new_pfx, binary)) < 0)
return error;
pi->line.origin = GIT_DIFF_LINE_BINARY;
pi->line.content = git_buf_cstr(pi->buf);
pi->line.content_len = git_buf_len(pi->buf);
return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
static int diff_print_patch_hunk(
const git_diff_delta *d,
const git_diff_hunk *h,
void *data)
{
diff_print_info *pi = data;
if (S_ISDIR(d->new_file.mode))
return 0;
pi->line.origin = GIT_DIFF_LINE_HUNK_HDR;
pi->line.content = h->header;
pi->line.content_len = h->header_len;
return pi->print_cb(d, h, &pi->line, pi->payload);
}
static int diff_print_patch_line(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *data)
{
diff_print_info *pi = data;
if (S_ISDIR(delta->new_file.mode))
return 0;
return pi->print_cb(delta, hunk, line, pi->payload);
}
/* print a git_diff to an output callback */
int git_diff_print(
git_diff *diff,
git_diff_format_t format,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf buf = GIT_BUF_INIT;
diff_print_info pi;
git_diff_file_cb print_file = NULL;
git_diff_binary_cb print_binary = NULL;
git_diff_hunk_cb print_hunk = NULL;
git_diff_line_cb print_line = NULL;
switch (format) {
case GIT_DIFF_FORMAT_PATCH:
print_file = diff_print_patch_file;
print_binary = diff_print_patch_binary;
print_hunk = diff_print_patch_hunk;
print_line = diff_print_patch_line;
break;
case GIT_DIFF_FORMAT_PATCH_ID:
print_file = diff_print_patch_file;
print_binary = diff_print_patch_binary;
print_line = diff_print_patch_line;
break;
case GIT_DIFF_FORMAT_PATCH_HEADER:
print_file = diff_print_patch_file;
break;
case GIT_DIFF_FORMAT_RAW:
print_file = diff_print_one_raw;
break;
case GIT_DIFF_FORMAT_NAME_ONLY:
print_file = diff_print_one_name_only;
break;
case GIT_DIFF_FORMAT_NAME_STATUS:
print_file = diff_print_one_name_status;
break;
default:
git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format);
return -1;
}
if (!(error = diff_print_info_init_fromdiff(
&pi, &buf, diff, format, print_cb, payload))) {
error = git_diff_foreach(
diff, print_file, print_binary, print_hunk, print_line, &pi);
if (error) /* make sure error message is set */
git_error_set_after_callback_function(error, "git_diff_print");
}
git_buf_dispose(&buf);
return error;
}
int git_diff_print_callback__to_buf(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
git_buf *output = payload;
GIT_UNUSED(delta); GIT_UNUSED(hunk);
if (!output) {
git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided");
return -1;
}
if (line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION ||
line->origin == GIT_DIFF_LINE_CONTEXT)
git_buf_putc(output, line->origin);
return git_buf_put(output, line->content, line->content_len);
}
int git_diff_print_callback__to_file_handle(
const git_diff_delta *delta,
const git_diff_hunk *hunk,
const git_diff_line *line,
void *payload)
{
FILE *fp = payload ? payload : stdout;
GIT_UNUSED(delta); GIT_UNUSED(hunk);
if (line->origin == GIT_DIFF_LINE_CONTEXT ||
line->origin == GIT_DIFF_LINE_ADDITION ||
line->origin == GIT_DIFF_LINE_DELETION)
fputc(line->origin, fp);
fwrite(line->content, 1, line->content_len, fp);
return 0;
}
/* print a git_diff to a git_buf */
int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
{
assert(out && diff);
git_buf_sanitize(out);
return git_diff_print(
diff, format, git_diff_print_callback__to_buf, out);
}
/* print a git_patch to an output callback */
int git_patch_print(
git_patch *patch,
git_diff_line_cb print_cb,
void *payload)
{
int error;
git_buf temp = GIT_BUF_INIT;
diff_print_info pi;
assert(patch && print_cb);
if (!(error = diff_print_info_init_frompatch(
&pi, &temp, patch,
GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
{
error = git_patch__invoke_callbacks(
patch,
diff_print_patch_file, diff_print_patch_binary,
diff_print_patch_hunk, diff_print_patch_line,
&pi);
if (error) /* make sure error message is set */
git_error_set_after_callback_function(error, "git_patch_print");
}
git_buf_dispose(&temp);
return error;
}
/* print a git_patch to a git_buf */
int git_patch_to_buf(git_buf *out, git_patch *patch)
{
assert(out && patch);
git_buf_sanitize(out);
return git_patch_print(patch, git_diff_print_callback__to_buf, out);
}
+362
View File
@@ -0,0 +1,362 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "vector.h"
#include "diff.h"
#include "patch_generate.h"
#define DIFF_RENAME_FILE_SEPARATOR " => "
#define STATS_FULL_MIN_SCALE 7
typedef struct {
size_t insertions;
size_t deletions;
} diff_file_stats;
struct git_diff_stats {
git_diff *diff;
diff_file_stats *filestats;
size_t files_changed;
size_t insertions;
size_t deletions;
size_t renames;
size_t max_name;
size_t max_filestat;
int max_digits;
};
static int digits_for_value(size_t val)
{
int count = 1;
size_t placevalue = 10;
while (val >= placevalue) {
++count;
placevalue *= 10;
}
return count;
}
int git_diff_file_stats__full_to_buf(
git_buf *out,
const git_diff_delta *delta,
const diff_file_stats *filestat,
const git_diff_stats *stats,
size_t width)
{
const char *old_path = NULL, *new_path = NULL;
size_t padding;
git_object_size_t old_size, new_size;
old_path = delta->old_file.path;
new_path = delta->new_file.path;
old_size = delta->old_file.size;
new_size = delta->new_file.size;
if (strcmp(old_path, new_path) != 0) {
size_t common_dirlen;
int error;
padding = stats->max_name - strlen(old_path) - strlen(new_path);
if ((common_dirlen = git_path_common_dirlen(old_path, new_path)) &&
common_dirlen <= INT_MAX) {
error = git_buf_printf(out, " %.*s{%s"DIFF_RENAME_FILE_SEPARATOR"%s}",
(int) common_dirlen, old_path,
old_path + common_dirlen,
new_path + common_dirlen);
} else {
error = git_buf_printf(out, " %s" DIFF_RENAME_FILE_SEPARATOR "%s",
old_path, new_path);
}
if (error < 0)
goto on_error;
} else {
if (git_buf_printf(out, " %s", old_path) < 0)
goto on_error;
padding = stats->max_name - strlen(old_path);
if (stats->renames > 0)
padding += strlen(DIFF_RENAME_FILE_SEPARATOR);
}
if (git_buf_putcn(out, ' ', padding) < 0 ||
git_buf_puts(out, " | ") < 0)
goto on_error;
if (delta->flags & GIT_DIFF_FLAG_BINARY) {
if (git_buf_printf(out,
"Bin %" PRId64 " -> %" PRId64 " bytes", old_size, new_size) < 0)
goto on_error;
}
else {
if (git_buf_printf(out,
"%*" PRIuZ, stats->max_digits,
filestat->insertions + filestat->deletions) < 0)
goto on_error;
if (filestat->insertions || filestat->deletions) {
if (git_buf_putc(out, ' ') < 0)
goto on_error;
if (!width) {
if (git_buf_putcn(out, '+', filestat->insertions) < 0 ||
git_buf_putcn(out, '-', filestat->deletions) < 0)
goto on_error;
} else {
size_t total = filestat->insertions + filestat->deletions;
size_t full = (total * width + stats->max_filestat / 2) /
stats->max_filestat;
size_t plus = full * filestat->insertions / total;
size_t minus = full - plus;
if (git_buf_putcn(out, '+', max(plus, 1)) < 0 ||
git_buf_putcn(out, '-', max(minus, 1)) < 0)
goto on_error;
}
}
}
git_buf_putc(out, '\n');
on_error:
return (git_buf_oom(out) ? -1 : 0);
}
int git_diff_file_stats__number_to_buf(
git_buf *out,
const git_diff_delta *delta,
const diff_file_stats *filestats)
{
int error;
const char *path = delta->new_file.path;
if (delta->flags & GIT_DIFF_FLAG_BINARY)
error = git_buf_printf(out, "%-8c" "%-8c" "%s\n", '-', '-', path);
else
error = git_buf_printf(out, "%-8" PRIuZ "%-8" PRIuZ "%s\n",
filestats->insertions, filestats->deletions, path);
return error;
}
int git_diff_file_stats__summary_to_buf(
git_buf *out,
const git_diff_delta *delta)
{
if (delta->old_file.mode != delta->new_file.mode) {
if (delta->old_file.mode == 0) {
git_buf_printf(out, " create mode %06o %s\n",
delta->new_file.mode, delta->new_file.path);
}
else if (delta->new_file.mode == 0) {
git_buf_printf(out, " delete mode %06o %s\n",
delta->old_file.mode, delta->old_file.path);
}
else {
git_buf_printf(out, " mode change %06o => %06o %s\n",
delta->old_file.mode, delta->new_file.mode, delta->new_file.path);
}
}
return 0;
}
int git_diff_get_stats(
git_diff_stats **out,
git_diff *diff)
{
size_t i, deltas;
size_t total_insertions = 0, total_deletions = 0;
git_diff_stats *stats = NULL;
int error = 0;
assert(out && diff);
stats = git__calloc(1, sizeof(git_diff_stats));
GIT_ERROR_CHECK_ALLOC(stats);
deltas = git_diff_num_deltas(diff);
stats->filestats = git__calloc(deltas, sizeof(diff_file_stats));
if (!stats->filestats) {
git__free(stats);
return -1;
}
stats->diff = diff;
GIT_REFCOUNT_INC(diff);
for (i = 0; i < deltas && !error; ++i) {
git_patch *patch = NULL;
size_t add = 0, remove = 0, namelen;
const git_diff_delta *delta;
if ((error = git_patch_from_diff(&patch, diff, i)) < 0)
break;
/* keep a count of renames because it will affect formatting */
delta = patch->delta;
/* TODO ugh */
namelen = strlen(delta->new_file.path);
if (strcmp(delta->old_file.path, delta->new_file.path) != 0) {
namelen += strlen(delta->old_file.path);
stats->renames++;
}
/* and, of course, count the line stats */
error = git_patch_line_stats(NULL, &add, &remove, patch);
git_patch_free(patch);
stats->filestats[i].insertions = add;
stats->filestats[i].deletions = remove;
total_insertions += add;
total_deletions += remove;
if (stats->max_name < namelen)
stats->max_name = namelen;
if (stats->max_filestat < add + remove)
stats->max_filestat = add + remove;
}
stats->files_changed = deltas;
stats->insertions = total_insertions;
stats->deletions = total_deletions;
stats->max_digits = digits_for_value(stats->max_filestat + 1);
if (error < 0) {
git_diff_stats_free(stats);
stats = NULL;
}
*out = stats;
return error;
}
size_t git_diff_stats_files_changed(
const git_diff_stats *stats)
{
assert(stats);
return stats->files_changed;
}
size_t git_diff_stats_insertions(
const git_diff_stats *stats)
{
assert(stats);
return stats->insertions;
}
size_t git_diff_stats_deletions(
const git_diff_stats *stats)
{
assert(stats);
return stats->deletions;
}
int git_diff_stats_to_buf(
git_buf *out,
const git_diff_stats *stats,
git_diff_stats_format_t format,
size_t width)
{
int error = 0;
size_t i;
const git_diff_delta *delta;
assert(out && stats);
if (format & GIT_DIFF_STATS_NUMBER) {
for (i = 0; i < stats->files_changed; ++i) {
if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
continue;
error = git_diff_file_stats__number_to_buf(
out, delta, &stats->filestats[i]);
if (error < 0)
return error;
}
}
if (format & GIT_DIFF_STATS_FULL) {
if (width > 0) {
if (width > stats->max_name + stats->max_digits + 5)
width -= (stats->max_name + stats->max_digits + 5);
if (width < STATS_FULL_MIN_SCALE)
width = STATS_FULL_MIN_SCALE;
}
if (width > stats->max_filestat)
width = 0;
for (i = 0; i < stats->files_changed; ++i) {
if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
continue;
error = git_diff_file_stats__full_to_buf(
out, delta, &stats->filestats[i], stats, width);
if (error < 0)
return error;
}
}
if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) {
git_buf_printf(
out, " %" PRIuZ " file%s changed",
stats->files_changed, stats->files_changed != 1 ? "s" : "");
if (stats->insertions || stats->deletions == 0)
git_buf_printf(
out, ", %" PRIuZ " insertion%s(+)",
stats->insertions, stats->insertions != 1 ? "s" : "");
if (stats->deletions || stats->insertions == 0)
git_buf_printf(
out, ", %" PRIuZ " deletion%s(-)",
stats->deletions, stats->deletions != 1 ? "s" : "");
git_buf_putc(out, '\n');
if (git_buf_oom(out))
return -1;
}
if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) {
for (i = 0; i < stats->files_changed; ++i) {
if ((delta = git_diff_get_delta(stats->diff, i)) == NULL)
continue;
error = git_diff_file_stats__summary_to_buf(out, delta);
if (error < 0)
return error;
}
}
return error;
}
void git_diff_stats_free(git_diff_stats *stats)
{
if (stats == NULL)
return;
git_diff_free(stats->diff); /* bumped refcount in constructor */
git__free(stats->filestats);
git__free(stats);
}
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_tform_h__
#define INCLUDE_diff_tform_h__
#include "common.h"
#include "diff_file.h"
extern int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p);
extern int git_diff_find_similar__hashsig_for_buf(
void **out, const git_diff_file *f, const char *buf, size_t len, void *p);
extern void git_diff_find_similar__hashsig_free(void *sig, void *payload);
extern int git_diff_find_similar__calc_similarity(
int *score, void *siga, void *sigb, void *payload);
#endif
+263
View File
@@ -0,0 +1,263 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "diff_xdiff.h"
#include "util.h"
#include "git2/errors.h"
#include "diff.h"
#include "diff_driver.h"
#include "patch_generate.h"
static int git_xdiff_scan_int(const char **str, int *value)
{
const char *scan = *str;
int v = 0, digits = 0;
/* find next digit */
for (scan = *str; *scan && !git__isdigit(*scan); scan++);
/* parse next number */
for (; git__isdigit(*scan); scan++, digits++)
v = (v * 10) + (*scan - '0');
*str = scan;
*value = v;
return (digits > 0) ? 0 : -1;
}
static int git_xdiff_parse_hunk(git_diff_hunk *hunk, const char *header)
{
/* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */
if (*header != '@')
goto fail;
if (git_xdiff_scan_int(&header, &hunk->old_start) < 0)
goto fail;
if (*header == ',') {
if (git_xdiff_scan_int(&header, &hunk->old_lines) < 0)
goto fail;
} else
hunk->old_lines = 1;
if (git_xdiff_scan_int(&header, &hunk->new_start) < 0)
goto fail;
if (*header == ',') {
if (git_xdiff_scan_int(&header, &hunk->new_lines) < 0)
goto fail;
} else
hunk->new_lines = 1;
if (hunk->old_start < 0 || hunk->new_start < 0)
goto fail;
return 0;
fail:
git_error_set(GIT_ERROR_INVALID, "malformed hunk header from xdiff");
return -1;
}
typedef struct {
git_xdiff_output *xo;
git_patch_generated *patch;
git_diff_hunk hunk;
int old_lineno, new_lineno;
mmfile_t xd_old_data, xd_new_data;
} git_xdiff_info;
static int diff_update_lines(
git_xdiff_info *info,
git_diff_line *line,
const char *content,
size_t content_len)
{
const char *scan = content, *scan_end = content + content_len;
for (line->num_lines = 0; scan < scan_end; ++scan)
if (*scan == '\n')
++line->num_lines;
line->content = content;
line->content_len = content_len;
/* expect " "/"-"/"+", then data */
switch (line->origin) {
case GIT_DIFF_LINE_ADDITION:
case GIT_DIFF_LINE_DEL_EOFNL:
line->old_lineno = -1;
line->new_lineno = info->new_lineno;
info->new_lineno += (int)line->num_lines;
break;
case GIT_DIFF_LINE_DELETION:
case GIT_DIFF_LINE_ADD_EOFNL:
line->old_lineno = info->old_lineno;
line->new_lineno = -1;
info->old_lineno += (int)line->num_lines;
break;
case GIT_DIFF_LINE_CONTEXT:
case GIT_DIFF_LINE_CONTEXT_EOFNL:
line->old_lineno = info->old_lineno;
line->new_lineno = info->new_lineno;
info->old_lineno += (int)line->num_lines;
info->new_lineno += (int)line->num_lines;
break;
default:
git_error_set(GIT_ERROR_INVALID, "unknown diff line origin %02x",
(unsigned int)line->origin);
return -1;
}
return 0;
}
static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len)
{
git_xdiff_info *info = priv;
git_patch_generated *patch = info->patch;
const git_diff_delta *delta = patch->base.delta;
git_patch_generated_output *output = &info->xo->output;
git_diff_line line;
size_t buffer_len;
if (len == 1) {
output->error = git_xdiff_parse_hunk(&info->hunk, bufs[0].ptr);
if (output->error < 0)
return output->error;
info->hunk.header_len = bufs[0].size;
if (info->hunk.header_len >= sizeof(info->hunk.header))
info->hunk.header_len = sizeof(info->hunk.header) - 1;
/* Sanitize the hunk header in case there is invalid Unicode */
buffer_len = git__utf8_valid_buf_length((const uint8_t *) bufs[0].ptr, info->hunk.header_len);
/* Sanitizing the hunk header may delete the newline, so add it back again if there is room */
if (buffer_len < info->hunk.header_len) {
bufs[0].ptr[buffer_len] = '\n';
buffer_len += 1;
info->hunk.header_len = buffer_len;
}
memcpy(info->hunk.header, bufs[0].ptr, info->hunk.header_len);
info->hunk.header[info->hunk.header_len] = '\0';
if (output->hunk_cb != NULL &&
(output->error = output->hunk_cb(
delta, &info->hunk, output->payload)))
return output->error;
info->old_lineno = info->hunk.old_start;
info->new_lineno = info->hunk.new_start;
}
if (len == 2 || len == 3) {
/* expect " "/"-"/"+", then data */
line.origin =
(*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION :
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION :
GIT_DIFF_LINE_CONTEXT;
if (line.origin == GIT_DIFF_LINE_ADDITION)
line.content_offset = bufs[1].ptr - info->xd_new_data.ptr;
else if (line.origin == GIT_DIFF_LINE_DELETION)
line.content_offset = bufs[1].ptr - info->xd_old_data.ptr;
else
line.content_offset = -1;
output->error = diff_update_lines(
info, &line, bufs[1].ptr, bufs[1].size);
if (!output->error && output->data_cb != NULL)
output->error = output->data_cb(
delta, &info->hunk, &line, output->payload);
}
if (len == 3 && !output->error) {
/* If we have a '+' and a third buf, then we have added a line
* without a newline and the old code had one, so DEL_EOFNL.
* If we have a '-' and a third buf, then we have removed a line
* with out a newline but added a blank line, so ADD_EOFNL.
*/
line.origin =
(*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL :
(*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL :
GIT_DIFF_LINE_CONTEXT_EOFNL;
line.content_offset = -1;
output->error = diff_update_lines(
info, &line, bufs[2].ptr, bufs[2].size);
if (!output->error && output->data_cb != NULL)
output->error = output->data_cb(
delta, &info->hunk, &line, output->payload);
}
return output->error;
}
static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch)
{
git_xdiff_output *xo = (git_xdiff_output *)output;
git_xdiff_info info;
git_diff_find_context_payload findctxt;
memset(&info, 0, sizeof(info));
info.patch = patch;
info.xo = xo;
xo->callback.priv = &info;
git_diff_find_context_init(
&xo->config.find_func, &findctxt, git_patch_generated_driver(patch));
xo->config.find_func_priv = &findctxt;
if (xo->config.find_func != NULL)
xo->config.flags |= XDL_EMIT_FUNCNAMES;
else
xo->config.flags &= ~XDL_EMIT_FUNCNAMES;
/* TODO: check ofile.opts_flags to see if driver-specific per-file
* updates are needed to xo->params.flags
*/
git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch);
git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch);
if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE ||
info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) {
git_error_set(GIT_ERROR_INVALID, "files too large for diff");
return -1;
}
xdl_diff(&info.xd_old_data, &info.xd_new_data,
&xo->params, &xo->config, &xo->callback);
git_diff_find_context_clear(&findctxt);
return xo->output.error;
}
void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts)
{
uint32_t flags = opts ? opts->flags : 0;
xo->output.diff_cb = git_xdiff;
xo->config.ctxlen = opts ? opts->context_lines : 3;
xo->config.interhunkctxlen = opts ? opts->interhunk_lines : 0;
if (flags & GIT_DIFF_IGNORE_WHITESPACE)
xo->params.flags |= XDF_WHITESPACE_FLAGS;
if (flags & GIT_DIFF_IGNORE_WHITESPACE_CHANGE)
xo->params.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
if (flags & GIT_DIFF_IGNORE_WHITESPACE_EOL)
xo->params.flags |= XDF_IGNORE_WHITESPACE_AT_EOL;
if (flags & GIT_DIFF_INDENT_HEURISTIC)
xo->params.flags |= XDF_INDENT_HEURISTIC;
if (flags & GIT_DIFF_PATIENCE)
xo->params.flags |= XDF_PATIENCE_DIFF;
if (flags & GIT_DIFF_MINIMAL)
xo->params.flags |= XDF_NEED_MINIMAL;
xo->callback.outf = git_xdiff_cb;
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_diff_xdiff_h__
#define INCLUDE_diff_xdiff_h__
#include "common.h"
#include "diff.h"
#include "xdiff/xdiff.h"
#include "patch_generate.h"
/* xdiff cannot cope with large files. these files should not be passed to
* xdiff. callers should treat these large files as binary.
*/
#define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023)
/* A git_xdiff_output is a git_patch_generate_output with extra fields
* necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb
* field of the output to use xdiff to generate the diffs.
*/
typedef struct {
git_patch_generated_output output;
xdemitconf_t config;
xpparam_t params;
xdemitcb_t callback;
} git_xdiff_output;
void git_xdiff_init(git_xdiff_output *xo, const git_diff_options *opts);
#endif
+225
View File
@@ -0,0 +1,225 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "global.h"
#include "posix.h"
#include "buffer.h"
/********************************************
* New error handling
********************************************/
static git_error g_git_oom_error = {
"Out of memory",
GIT_ERROR_NOMEMORY
};
static void set_error_from_buffer(int error_class)
{
git_error *error = &GIT_GLOBAL->error_t;
git_buf *buf = &GIT_GLOBAL->error_buf;
error->message = buf->ptr;
error->klass = error_class;
GIT_GLOBAL->last_error = error;
}
static void set_error(int error_class, char *string)
{
git_buf *buf = &GIT_GLOBAL->error_buf;
git_buf_clear(buf);
if (string) {
git_buf_puts(buf, string);
git__free(string);
}
set_error_from_buffer(error_class);
}
void git_error_set_oom(void)
{
GIT_GLOBAL->last_error = &g_git_oom_error;
}
void git_error_set(int error_class, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
git_error_vset(error_class, fmt, ap);
va_end(ap);
}
void git_error_vset(int error_class, const char *fmt, va_list ap)
{
#ifdef GIT_WIN32
DWORD win32_error_code = (error_class == GIT_ERROR_OS) ? GetLastError() : 0;
#endif
int error_code = (error_class == GIT_ERROR_OS) ? errno : 0;
git_buf *buf = &GIT_GLOBAL->error_buf;
git_buf_clear(buf);
if (fmt) {
git_buf_vprintf(buf, fmt, ap);
if (error_class == GIT_ERROR_OS)
git_buf_PUTS(buf, ": ");
}
if (error_class == GIT_ERROR_OS) {
#ifdef GIT_WIN32
char * win32_error = git_win32_get_error_message(win32_error_code);
if (win32_error) {
git_buf_puts(buf, win32_error);
git__free(win32_error);
SetLastError(0);
}
else
#endif
if (error_code)
git_buf_puts(buf, strerror(error_code));
if (error_code)
errno = 0;
}
if (!git_buf_oom(buf))
set_error_from_buffer(error_class);
}
void git_error_set_str(int error_class, const char *string)
{
git_buf *buf = &GIT_GLOBAL->error_buf;
assert(string);
if (!string)
return;
git_buf_clear(buf);
git_buf_puts(buf, string);
if (!git_buf_oom(buf))
set_error_from_buffer(error_class);
}
void git_error_clear(void)
{
if (GIT_GLOBAL->last_error != NULL) {
set_error(0, NULL);
GIT_GLOBAL->last_error = NULL;
}
errno = 0;
#ifdef GIT_WIN32
SetLastError(0);
#endif
}
const git_error *git_error_last(void)
{
return GIT_GLOBAL->last_error;
}
int git_error_state_capture(git_error_state *state, int error_code)
{
git_error *error = GIT_GLOBAL->last_error;
git_buf *error_buf = &GIT_GLOBAL->error_buf;
memset(state, 0, sizeof(git_error_state));
if (!error_code)
return 0;
state->error_code = error_code;
state->oom = (error == &g_git_oom_error);
if (error) {
state->error_msg.klass = error->klass;
if (state->oom)
state->error_msg.message = g_git_oom_error.message;
else
state->error_msg.message = git_buf_detach(error_buf);
}
git_error_clear();
return error_code;
}
int git_error_state_restore(git_error_state *state)
{
int ret = 0;
git_error_clear();
if (state && state->error_msg.message) {
if (state->oom)
git_error_set_oom();
else
set_error(state->error_msg.klass, state->error_msg.message);
ret = state->error_code;
memset(state, 0, sizeof(git_error_state));
}
return ret;
}
void git_error_state_free(git_error_state *state)
{
if (!state)
return;
if (!state->oom)
git__free(state->error_msg.message);
memset(state, 0, sizeof(git_error_state));
}
int git_error_system_last(void)
{
#ifdef GIT_WIN32
return GetLastError();
#else
return errno;
#endif
}
void git_error_system_set(int code)
{
#ifdef GIT_WIN32
SetLastError(code);
#else
errno = code;
#endif
}
/* Deprecated error values and functions */
const git_error *giterr_last(void)
{
return git_error_last();
}
void giterr_clear(void)
{
git_error_clear();
}
void giterr_set_str(int error_class, const char *string)
{
git_error_set_str(error_class, string);
}
void giterr_set_oom(void)
{
git_error_set_oom();
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_errors_h__
#define INCLUDE_errors_h__
#include "common.h"
/*
* Set the error message for this thread, formatting as needed.
*/
void git_error_set(int error_class, const char *fmt, ...) GIT_FORMAT_PRINTF(2, 3);
void git_error_vset(int error_class, const char *fmt, va_list ap);
/**
* Set error message for user callback if needed.
*
* If the error code in non-zero and no error message is set, this
* sets a generic error message.
*
* @return This always returns the `error_code` parameter.
*/
GIT_INLINE(int) git_error_set_after_callback_function(
int error_code, const char *action)
{
if (error_code) {
const git_error *e = git_error_last();
if (!e || !e->message)
git_error_set(e ? e->klass : GIT_ERROR_CALLBACK,
"%s callback returned %d", action, error_code);
}
return error_code;
}
#ifdef GIT_WIN32
#define git_error_set_after_callback(code) \
git_error_set_after_callback_function((code), __FUNCTION__)
#else
#define git_error_set_after_callback(code) \
git_error_set_after_callback_function((code), __func__)
#endif
/**
* Gets the system error code for this thread.
*/
int git_error_system_last(void);
/**
* Sets the system error code for this thread.
*/
void git_error_system_set(int code);
/**
* Structure to preserve libgit2 error state
*/
typedef struct {
int error_code;
unsigned int oom : 1;
git_error error_msg;
} git_error_state;
/**
* Capture current error state to restore later, returning error code.
* If `error_code` is zero, this does not clear the current error state.
* You must either restore this error state, or free it.
*/
extern int git_error_state_capture(git_error_state *state, int error_code);
/**
* Restore error state to a previous value, returning saved error code.
*/
extern int git_error_state_restore(git_error_state *state);
/** Free an error state. */
extern void git_error_state_free(git_error_state *state);
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef INCLUDE_features_h__
#define INCLUDE_features_h__
#cmakedefine GIT_DEBUG_POOL 1
#cmakedefine GIT_TRACE 1
#cmakedefine GIT_THREADS 1
#cmakedefine GIT_MSVC_CRTDBG 1
#cmakedefine GIT_ARCH_64 1
#cmakedefine GIT_ARCH_32 1
#cmakedefine GIT_USE_ICONV 1
#cmakedefine GIT_USE_NSEC 1
#cmakedefine GIT_USE_STAT_MTIM 1
#cmakedefine GIT_USE_STAT_MTIMESPEC 1
#cmakedefine GIT_USE_STAT_MTIME_NSEC 1
#cmakedefine GIT_USE_FUTIMENS 1
#cmakedefine GIT_REGEX_REGCOMP_L
#cmakedefine GIT_REGEX_REGCOMP
#cmakedefine GIT_REGEX_PCRE
#cmakedefine GIT_REGEX_PCRE2
#cmakedefine GIT_REGEX_BUILTIN 1
#cmakedefine GIT_SSH 1
#cmakedefine GIT_SSH_MEMORY_CREDENTIALS 1
#cmakedefine GIT_NTLM 1
#cmakedefine GIT_GSSAPI 1
#cmakedefine GIT_GSSFRAMEWORK 1
#cmakedefine GIT_WINHTTP 1
#cmakedefine GIT_HTTPS 1
#cmakedefine GIT_OPENSSL 1
#cmakedefine GIT_SECURE_TRANSPORT 1
#cmakedefine GIT_MBEDTLS 1
#cmakedefine GIT_SHA1_COLLISIONDETECT 1
#cmakedefine GIT_SHA1_WIN32 1
#cmakedefine GIT_SHA1_COMMON_CRYPTO 1
#cmakedefine GIT_SHA1_OPENSSL 1
#cmakedefine GIT_SHA1_MBEDTLS 1
#endif
+161
View File
@@ -0,0 +1,161 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "fetch.h"
#include "git2/oid.h"
#include "git2/refs.h"
#include "git2/revwalk.h"
#include "git2/transport.h"
#include "remote.h"
#include "refspec.h"
#include "pack.h"
#include "netops.h"
#include "repository.h"
#include "refs.h"
static int maybe_want(git_remote *remote, git_remote_head *head, git_odb *odb, git_refspec *tagspec, git_remote_autotag_option_t tagopt)
{
int match = 0;
if (!git_reference_is_valid_name(head->name))
return 0;
if (tagopt == GIT_REMOTE_DOWNLOAD_TAGS_ALL) {
/*
* If tagopt is --tags, always request tags
* in addition to the remote's refspecs
*/
if (git_refspec_src_matches(tagspec, head->name))
match = 1;
}
if (!match && git_remote__matching_refspec(remote, head->name))
match = 1;
if (!match)
return 0;
/* If we have the object, mark it so we don't ask for it */
if (git_odb_exists(odb, &head->oid)) {
head->local = 1;
}
else
remote->need_pack = 1;
return git_vector_insert(&remote->refs, head);
}
static int filter_wants(git_remote *remote, const git_fetch_options *opts)
{
git_remote_head **heads;
git_refspec tagspec, head;
int error = 0;
git_odb *odb;
size_t i, heads_len;
git_remote_autotag_option_t tagopt = remote->download_tags;
if (opts && opts->download_tags != GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED)
tagopt = opts->download_tags;
git_vector_clear(&remote->refs);
if ((error = git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true)) < 0)
return error;
/*
* 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.
*/
if (remote->active_refspecs.length == 0) {
if ((error = git_refspec__parse(&head, "HEAD", true)) < 0)
goto cleanup;
error = git_refspec__dwim_one(&remote->active_refspecs, &head, &remote->refs);
git_refspec__dispose(&head);
if (error < 0)
goto cleanup;
}
if (git_repository_odb__weakptr(&odb, remote->repo) < 0)
goto cleanup;
if (git_remote_ls((const git_remote_head ***)&heads, &heads_len, remote) < 0)
goto cleanup;
for (i = 0; i < heads_len; i++) {
if ((error = maybe_want(remote, heads[i], odb, &tagspec, tagopt)) < 0)
break;
}
cleanup:
git_refspec__dispose(&tagspec);
return error;
}
/*
* 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, const git_fetch_options *opts)
{
git_transport *t = remote->transport;
remote->need_pack = 0;
if (filter_wants(remote, opts) < 0) {
git_error_set(GIT_ERROR_NET, "failed to filter the reference list for wants");
return -1;
}
/* Don't try to negotiate when we don't want anything */
if (!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,
(const git_remote_head * const *)remote->refs.contents,
remote->refs.length);
}
int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks)
{
git_transport *t = remote->transport;
git_indexer_progress_cb progress = NULL;
void *payload = NULL;
if (!remote->need_pack)
return 0;
if (callbacks) {
progress = callbacks->transfer_progress;
payload = callbacks->payload;
}
return t->download_pack(t, remote->repo, &remote->stats, progress, payload);
}
int git_fetch_options_init(git_fetch_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_fetch_options, GIT_FETCH_OPTIONS_INIT);
return 0;
}
int git_fetch_init_options(git_fetch_options *opts, unsigned int version)
{
return git_fetch_options_init(opts, version);
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_fetch_h__
#define INCLUDE_fetch_h__
#include "common.h"
#include "git2/remote.h"
#include "netops.h"
int git_fetch_negotiate(git_remote *remote, const git_fetch_options *opts);
int git_fetch_download_pack(git_remote *remote, const git_remote_callbacks *callbacks);
int git_fetch_setup_walk(git_revwalk **out, git_repository *repo);
#endif
+302
View File
@@ -0,0 +1,302 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "fetchhead.h"
#include "git2/types.h"
#include "git2/oid.h"
#include "buffer.h"
#include "futils.h"
#include "filebuf.h"
#include "refs.h"
#include "repository.h"
int git_fetchhead_ref_cmp(const void *a, const void *b)
{
const git_fetchhead_ref *one = (const git_fetchhead_ref *)a;
const git_fetchhead_ref *two = (const git_fetchhead_ref *)b;
if (one->is_merge && !two->is_merge)
return -1;
if (two->is_merge && !one->is_merge)
return 1;
if (one->ref_name && two->ref_name)
return strcmp(one->ref_name, two->ref_name);
else if (one->ref_name)
return -1;
else if (two->ref_name)
return 1;
return 0;
}
int git_fetchhead_ref_create(
git_fetchhead_ref **out,
git_oid *oid,
unsigned int is_merge,
const char *ref_name,
const char *remote_url)
{
git_fetchhead_ref *fetchhead_ref;
assert(out && oid);
*out = NULL;
fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref));
GIT_ERROR_CHECK_ALLOC(fetchhead_ref);
memset(fetchhead_ref, 0x0, sizeof(git_fetchhead_ref));
git_oid_cpy(&fetchhead_ref->oid, oid);
fetchhead_ref->is_merge = is_merge;
if (ref_name)
fetchhead_ref->ref_name = git__strdup(ref_name);
if (remote_url)
fetchhead_ref->remote_url = git__strdup(remote_url);
*out = fetchhead_ref;
return 0;
}
static int fetchhead_ref_write(
git_filebuf *file,
git_fetchhead_ref *fetchhead_ref)
{
char oid[GIT_OID_HEXSZ + 1];
const char *type, *name;
int head = 0;
assert(file && fetchhead_ref);
git_oid_fmt(oid, &fetchhead_ref->oid);
oid[GIT_OID_HEXSZ] = '\0';
if (git__prefixcmp(fetchhead_ref->ref_name, GIT_REFS_HEADS_DIR) == 0) {
type = "branch ";
name = fetchhead_ref->ref_name + strlen(GIT_REFS_HEADS_DIR);
} else if(git__prefixcmp(fetchhead_ref->ref_name,
GIT_REFS_TAGS_DIR) == 0) {
type = "tag ";
name = fetchhead_ref->ref_name + strlen(GIT_REFS_TAGS_DIR);
} else if (!git__strcmp(fetchhead_ref->ref_name, GIT_HEAD_FILE)) {
head = 1;
} else {
type = "";
name = fetchhead_ref->ref_name;
}
if (head)
return git_filebuf_printf(file, "%s\t\t%s\n", oid, fetchhead_ref->remote_url);
return git_filebuf_printf(file, "%s\t%s\t%s'%s' of %s\n",
oid,
(fetchhead_ref->is_merge) ? "" : "not-for-merge",
type,
name,
fetchhead_ref->remote_url);
}
int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs)
{
git_filebuf file = GIT_FILEBUF_INIT;
git_buf path = GIT_BUF_INIT;
unsigned int i;
git_fetchhead_ref *fetchhead_ref;
assert(repo && fetchhead_refs);
if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
return -1;
if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_APPEND, GIT_REFS_FILE_MODE) < 0) {
git_buf_dispose(&path);
return -1;
}
git_buf_dispose(&path);
git_vector_sort(fetchhead_refs);
git_vector_foreach(fetchhead_refs, i, fetchhead_ref)
fetchhead_ref_write(&file, fetchhead_ref);
return git_filebuf_commit(&file);
}
static int fetchhead_ref_parse(
git_oid *oid,
unsigned int *is_merge,
git_buf *ref_name,
const char **remote_url,
char *line,
size_t line_num)
{
char *oid_str, *is_merge_str, *desc, *name = NULL;
const char *type = NULL;
int error = 0;
*remote_url = NULL;
if (!*line) {
git_error_set(GIT_ERROR_FETCHHEAD,
"empty line in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
/* Compat with old git clients that wrote FETCH_HEAD like a loose ref. */
if ((oid_str = git__strsep(&line, "\t")) == NULL) {
oid_str = line;
line += strlen(line);
*is_merge = 1;
}
if (strlen(oid_str) != GIT_OID_HEXSZ) {
git_error_set(GIT_ERROR_FETCHHEAD,
"invalid object ID in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
if (git_oid_fromstr(oid, oid_str) < 0) {
const git_error *oid_err = git_error_last();
const char *err_msg = oid_err ? oid_err->message : "invalid object ID";
git_error_set(GIT_ERROR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ,
err_msg, line_num);
return -1;
}
/* Parse new data from newer git clients */
if (*line) {
if ((is_merge_str = git__strsep(&line, "\t")) == NULL) {
git_error_set(GIT_ERROR_FETCHHEAD,
"invalid description data in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
if (*is_merge_str == '\0')
*is_merge = 1;
else if (strcmp(is_merge_str, "not-for-merge") == 0)
*is_merge = 0;
else {
git_error_set(GIT_ERROR_FETCHHEAD,
"invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
if ((desc = line) == NULL) {
git_error_set(GIT_ERROR_FETCHHEAD,
"invalid description in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
if (git__prefixcmp(desc, "branch '") == 0) {
type = GIT_REFS_HEADS_DIR;
name = desc + 8;
} else if (git__prefixcmp(desc, "tag '") == 0) {
type = GIT_REFS_TAGS_DIR;
name = desc + 5;
} else if (git__prefixcmp(desc, "'") == 0)
name = desc + 1;
if (name) {
if ((desc = strstr(name, "' ")) == NULL ||
git__prefixcmp(desc, "' of ") != 0) {
git_error_set(GIT_ERROR_FETCHHEAD,
"invalid description in FETCH_HEAD line %"PRIuZ, line_num);
return -1;
}
*desc = '\0';
desc += 5;
}
*remote_url = desc;
}
git_buf_clear(ref_name);
if (type)
git_buf_join(ref_name, '/', type, name);
else if(name)
git_buf_puts(ref_name, name);
return error;
}
int git_repository_fetchhead_foreach(git_repository *repo,
git_repository_fetchhead_foreach_cb cb,
void *payload)
{
git_buf path = GIT_BUF_INIT, file = GIT_BUF_INIT, name = GIT_BUF_INIT;
const char *ref_name;
git_oid oid;
const char *remote_url;
unsigned int is_merge = 0;
char *buffer, *line;
size_t line_num = 0;
int error = 0;
assert(repo && cb);
if (git_buf_joinpath(&path, repo->gitdir, GIT_FETCH_HEAD_FILE) < 0)
return -1;
if ((error = git_futils_readbuffer(&file, git_buf_cstr(&path))) < 0)
goto done;
buffer = file.ptr;
while ((line = git__strsep(&buffer, "\n")) != NULL) {
++line_num;
if ((error = fetchhead_ref_parse(
&oid, &is_merge, &name, &remote_url, line, line_num)) < 0)
goto done;
if (git_buf_len(&name) > 0)
ref_name = git_buf_cstr(&name);
else
ref_name = NULL;
error = cb(ref_name, remote_url, &oid, is_merge, payload);
if (error) {
git_error_set_after_callback(error);
goto done;
}
}
if (*buffer) {
git_error_set(GIT_ERROR_FETCHHEAD, "no EOL at line %"PRIuZ, line_num+1);
error = -1;
goto done;
}
done:
git_buf_dispose(&file);
git_buf_dispose(&path);
git_buf_dispose(&name);
return error;
}
void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref)
{
if (fetchhead_ref == NULL)
return;
git__free(fetchhead_ref->remote_url);
git__free(fetchhead_ref->ref_name);
git__free(fetchhead_ref);
}
+35
View File
@@ -0,0 +1,35 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_fetchhead_h__
#define INCLUDE_fetchhead_h__
#include "common.h"
#include "oid.h"
#include "vector.h"
typedef struct git_fetchhead_ref {
git_oid oid;
unsigned int is_merge;
char *ref_name;
char *remote_url;
} git_fetchhead_ref;
int git_fetchhead_ref_create(
git_fetchhead_ref **fetchhead_ref_out,
git_oid *oid,
unsigned int is_merge,
const char *ref_name,
const char *remote_url);
int git_fetchhead_ref_cmp(const void *a, const void *b);
int git_fetchhead_write(git_repository *repo, git_vector *fetchhead_refs);
void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref);
#endif
+593
View File
@@ -0,0 +1,593 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "filebuf.h"
#include "futils.h"
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:
git_error_set(GIT_ERROR_OS, "failed to write out file");
return -1;
case BUFERR_MEM:
git_error_set_oom();
return -1;
case BUFERR_ZLIB:
git_error_set(GIT_ERROR_ZLIB,
"Buffer error when writing out ZLib data");
return -1;
default:
return 0;
}
}
static int lock_file(git_filebuf *file, int flags, mode_t mode)
{
if (git_path_exists(file->path_lock) == true) {
git_error_clear(); /* actual OS error code just confuses */
git_error_set(GIT_ERROR_OS,
"failed to lock file '%s' for writing", file->path_lock);
return GIT_ELOCKED;
}
/* create path to the file buffer is required */
if (flags & GIT_FILEBUF_CREATE_LEADING_DIRS) {
/* XXX: Should dirmode here be configurable? Or is 0777 always fine? */
file->fd = git_futils_creat_locked_withpath(file->path_lock, 0777, mode);
} else {
file->fd = git_futils_creat_locked(file->path_lock, mode);
}
if (file->fd < 0)
return file->fd;
file->fd_is_open = true;
if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) {
git_file source;
char buffer[FILEIO_BUFSIZE];
ssize_t read_bytes;
int error = 0;
source = p_open(file->path_original, O_RDONLY);
if (source < 0) {
git_error_set(GIT_ERROR_OS,
"failed to open file '%s' for reading",
file->path_original);
return -1;
}
while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) {
if ((error = p_write(file->fd, buffer, read_bytes)) < 0)
break;
if (file->compute_digest)
git_hash_update(&file->digest, buffer, read_bytes);
}
p_close(source);
if (read_bytes < 0) {
git_error_set(GIT_ERROR_OS, "failed to read file '%s'", file->path_original);
return -1;
} else if (error < 0) {
git_error_set(GIT_ERROR_OS, "failed to write file '%s'", file->path_lock);
return -1;
}
}
return 0;
}
void git_filebuf_cleanup(git_filebuf *file)
{
if (file->fd_is_open && file->fd >= 0)
p_close(file->fd);
if (file->created_lock && !file->did_rename && file->path_lock && git_path_exists(file->path_lock))
p_unlink(file->path_lock);
if (file->compute_digest) {
git_hash_ctx_cleanup(&file->digest);
file->compute_digest = 0;
}
if (file->buffer)
git__free(file->buffer);
/* 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);
}
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)
{
int result = file->write(file, file->buffer, file->buf_pos);
file->buf_pos = 0;
return result;
}
int git_filebuf_flush(git_filebuf *file)
{
return flush_buffer(file);
}
static int write_normal(git_filebuf *file, void *source, size_t len)
{
if (len > 0) {
if (p_write(file->fd, (void *)source, len) < 0) {
file->last_error = BUFERR_WRITE;
return -1;
}
if (file->compute_digest)
git_hash_update(&file->digest, source, len);
}
return 0;
}
static int write_deflate(git_filebuf *file, void *source, size_t len)
{
z_stream *zs = &file->zs;
if (len > 0 || file->flush_mode == Z_FINISH) {
zs->next_in = source;
zs->avail_in = (uInt)len;
do {
size_t have;
zs->next_out = file->z_buf;
zs->avail_out = (uInt)file->buf_size;
if (deflate(zs, file->flush_mode) == Z_STREAM_ERROR) {
file->last_error = BUFERR_ZLIB;
return -1;
}
have = file->buf_size - (size_t)zs->avail_out;
if (p_write(file->fd, file->z_buf, have) < 0) {
file->last_error = BUFERR_WRITE;
return -1;
}
} while (zs->avail_out == 0);
assert(zs->avail_in == 0);
if (file->compute_digest)
git_hash_update(&file->digest, source, len);
}
return 0;
}
#define MAX_SYMLINK_DEPTH 5
static int resolve_symlink(git_buf *out, const char *path)
{
int i, error, root;
ssize_t ret;
struct stat st;
git_buf curpath = GIT_BUF_INIT, target = GIT_BUF_INIT;
if ((error = git_buf_grow(&target, GIT_PATH_MAX + 1)) < 0 ||
(error = git_buf_puts(&curpath, path)) < 0)
return error;
for (i = 0; i < MAX_SYMLINK_DEPTH; i++) {
error = p_lstat(curpath.ptr, &st);
if (error < 0 && errno == ENOENT) {
error = git_buf_puts(out, curpath.ptr);
goto cleanup;
}
if (error < 0) {
git_error_set(GIT_ERROR_OS, "failed to stat '%s'", curpath.ptr);
error = -1;
goto cleanup;
}
if (!S_ISLNK(st.st_mode)) {
error = git_buf_puts(out, curpath.ptr);
goto cleanup;
}
ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX);
if (ret < 0) {
git_error_set(GIT_ERROR_OS, "failed to read symlink '%s'", curpath.ptr);
error = -1;
goto cleanup;
}
if (ret == GIT_PATH_MAX) {
git_error_set(GIT_ERROR_INVALID, "symlink target too long");
error = -1;
goto cleanup;
}
/* readlink(2) won't NUL-terminate for us */
target.ptr[ret] = '\0';
target.size = ret;
root = git_path_root(target.ptr);
if (root >= 0) {
if ((error = git_buf_sets(&curpath, target.ptr)) < 0)
goto cleanup;
} else {
git_buf dir = GIT_BUF_INIT;
if ((error = git_path_dirname_r(&dir, curpath.ptr)) < 0)
goto cleanup;
git_buf_swap(&curpath, &dir);
git_buf_dispose(&dir);
if ((error = git_path_apply_relative(&curpath, target.ptr)) < 0)
goto cleanup;
}
}
git_error_set(GIT_ERROR_INVALID, "maximum symlink depth reached");
error = -1;
cleanup:
git_buf_dispose(&curpath);
git_buf_dispose(&target);
return error;
}
int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode)
{
return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE);
}
int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size)
{
int compression, error = -1;
size_t path_len, alloc_len;
/* 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;
if (flags & GIT_FILEBUF_FSYNC)
file->do_fsync = true;
file->buf_size = size;
file->buf_pos = 0;
file->fd = -1;
file->last_error = BUFERR_OK;
/* Allocate the main cache buffer */
if (!file->do_not_buffer) {
file->buffer = git__malloc(file->buf_size);
GIT_ERROR_CHECK_ALLOC(file->buffer);
}
/* If we are hashing on-write, allocate a new hash context */
if (flags & GIT_FILEBUF_HASH_CONTENTS) {
file->compute_digest = 1;
if (git_hash_ctx_init(&file->digest) < 0)
goto cleanup;
}
compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT;
/* If we are deflating on-write, */
if (compression != 0) {
/* Initialize the ZLib stream */
if (deflateInit(&file->zs, compression) != Z_OK) {
git_error_set(GIT_ERROR_ZLIB, "failed to initialize zlib");
goto cleanup;
}
/* Allocate the Zlib cache buffer */
file->z_buf = git__malloc(file->buf_size);
GIT_ERROR_CHECK_ALLOC(file->z_buf);
/* Never flush */
file->flush_mode = Z_NO_FLUSH;
file->write = &write_deflate;
} else {
file->write = &write_normal;
}
/* If we are writing to a temp file */
if (flags & GIT_FILEBUF_TEMPORARY) {
git_buf tmp_path = GIT_BUF_INIT;
/* Open the file as temporary for locking */
file->fd = git_futils_mktmp(&tmp_path, path, mode);
if (file->fd < 0) {
git_buf_dispose(&tmp_path);
goto cleanup;
}
file->fd_is_open = true;
file->created_lock = true;
/* No original path */
file->path_original = NULL;
file->path_lock = git_buf_detach(&tmp_path);
GIT_ERROR_CHECK_ALLOC(file->path_lock);
} else {
git_buf resolved_path = GIT_BUF_INIT;
if ((error = resolve_symlink(&resolved_path, path)) < 0)
goto cleanup;
/* Save the original path of the file */
path_len = resolved_path.size;
file->path_original = git_buf_detach(&resolved_path);
/* create the locking path by appending ".lock" to the original */
GIT_ERROR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH);
file->path_lock = git__malloc(alloc_len);
GIT_ERROR_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);
if (git_path_isdir(file->path_original)) {
git_error_set(GIT_ERROR_FILESYSTEM, "path '%s' is a directory", file->path_original);
error = GIT_EDIRECTORY;
goto cleanup;
}
/* open the file for locking */
if ((error = lock_file(file, flags, mode)) < 0)
goto cleanup;
file->created_lock = true;
}
return 0;
cleanup:
git_filebuf_cleanup(file);
return error;
}
int git_filebuf_hash(git_oid *oid, git_filebuf *file)
{
assert(oid && file && file->compute_digest);
flush_buffer(file);
if (verify_last_error(file) < 0)
return -1;
git_hash_final(oid, &file->digest);
git_hash_ctx_cleanup(&file->digest);
file->compute_digest = 0;
return 0;
}
int git_filebuf_commit_at(git_filebuf *file, const char *path)
{
git__free(file->path_original);
file->path_original = git__strdup(path);
GIT_ERROR_CHECK_ALLOC(file->path_original);
return git_filebuf_commit(file);
}
int git_filebuf_commit(git_filebuf *file)
{
/* temporary files cannot be committed */
assert(file && file->path_original);
file->flush_mode = Z_FINISH;
flush_buffer(file);
if (verify_last_error(file) < 0)
goto on_error;
file->fd_is_open = false;
if (file->do_fsync && p_fsync(file->fd) < 0) {
git_error_set(GIT_ERROR_OS, "failed to fsync '%s'", file->path_lock);
goto on_error;
}
if (p_close(file->fd) < 0) {
git_error_set(GIT_ERROR_OS, "failed to close file at '%s'", file->path_lock);
goto on_error;
}
file->fd = -1;
if (p_rename(file->path_lock, file->path_original) < 0) {
git_error_set(GIT_ERROR_OS, "failed to rename lockfile to '%s'", file->path_original);
goto on_error;
}
if (file->do_fsync && git_futils_fsync_parent(file->path_original) < 0)
goto on_error;
file->did_rename = true;
git_filebuf_cleanup(file);
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)
{
memcpy(file->buffer + file->buf_pos, buf, len);
file->buf_pos += len;
}
int git_filebuf_write(git_filebuf *file, const void *buff, size_t len)
{
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 0;
}
add_to_cache(file, buf, space_left);
if (flush_buffer(file) < 0)
return -1;
len -= space_left;
buf += space_left;
}
}
int git_filebuf_reserve(git_filebuf *file, void **buffer, size_t len)
{
size_t space_left = file->buf_size - file->buf_pos;
*buffer = NULL;
ENSURE_BUF_OK(file);
if (len > file->buf_size) {
file->last_error = BUFERR_MEM;
return -1;
}
if (space_left <= len) {
if (flush_buffer(file) < 0)
return -1;
}
*buffer = (file->buffer + file->buf_pos);
file->buf_pos += len;
return 0;
}
int git_filebuf_printf(git_filebuf *file, const char *format, ...)
{
va_list arglist;
size_t space_left, len, alloclen;
int written, res;
char *tmp_buffer;
ENSURE_BUF_OK(file);
space_left = file->buf_size - file->buf_pos;
do {
va_start(arglist, format);
written = p_vsnprintf((char *)file->buffer + file->buf_pos, space_left, format, arglist);
va_end(arglist);
if (written < 0) {
file->last_error = BUFERR_MEM;
return -1;
}
len = written;
if (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 (len + 1 <= space_left);
if (GIT_ADD_SIZET_OVERFLOW(&alloclen, len, 1) ||
!(tmp_buffer = git__malloc(alloclen))) {
file->last_error = BUFERR_MEM;
return -1;
}
va_start(arglist, format);
written = p_vsnprintf(tmp_buffer, len + 1, format, arglist);
va_end(arglist);
if (written < 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;
}
int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file)
{
int res;
struct stat st;
if (file->fd_is_open)
res = p_fstat(file->fd, &st);
else
res = p_stat(file->path_original, &st);
if (res < 0) {
git_error_set(GIT_ERROR_OS, "could not get stat info for '%s'",
file->path_original);
return res;
}
if (mtime)
*mtime = st.st_mtime;
if (size)
*size = (size_t)st.st_size;
return 0;
}
+94
View File
@@ -0,0 +1,94 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_filebuf_h__
#define INCLUDE_filebuf_h__
#include "common.h"
#include "futils.h"
#include "hash.h"
#include <zlib.h>
#ifdef GIT_THREADS
# define GIT_FILEBUF_THREADS
#endif
#define GIT_FILEBUF_HASH_CONTENTS (1 << 0)
#define GIT_FILEBUF_APPEND (1 << 2)
#define GIT_FILEBUF_CREATE_LEADING_DIRS (1 << 3)
#define GIT_FILEBUF_TEMPORARY (1 << 4)
#define GIT_FILEBUF_DO_NOT_BUFFER (1 << 5)
#define GIT_FILEBUF_FSYNC (1 << 6)
#define GIT_FILEBUF_DEFLATE_SHIFT (7)
#define GIT_FILELOCK_EXTENSION ".lock\0"
#define GIT_FILELOCK_EXTLENGTH 6
typedef struct git_filebuf git_filebuf;
struct git_filebuf {
char *path_original;
char *path_lock;
int (*write)(git_filebuf *file, void *source, size_t len);
bool compute_digest;
git_hash_ctx digest;
unsigned char *buffer;
unsigned char *z_buf;
z_stream zs;
int flush_mode;
size_t buf_size, buf_pos;
git_file fd;
bool fd_is_open;
bool created_lock;
bool did_rename;
bool do_not_buffer;
bool do_fsync;
int last_error;
};
#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, ...) GIT_FORMAT_PRINTF(2, 3);
int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode);
int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size);
int git_filebuf_commit(git_filebuf *lock);
int git_filebuf_commit_at(git_filebuf *lock, const char *path);
void git_filebuf_cleanup(git_filebuf *lock);
int git_filebuf_hash(git_oid *oid, git_filebuf *file);
int git_filebuf_flush(git_filebuf *file);
int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file);
#endif
+1045
View File
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_filter_h__
#define INCLUDE_filter_h__
#include "common.h"
#include "attr_file.h"
#include "git2/filter.h"
/* Amount of file to examine for NUL byte when checking binary-ness */
#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000
typedef struct {
git_attr_session *attr_session;
git_buf *temp_buf;
uint32_t flags;
} git_filter_options;
#define GIT_FILTER_OPTIONS_INIT {0}
extern int git_filter_global_init(void);
extern void git_filter_free(git_filter *filter);
extern int git_filter_list__load_ext(
git_filter_list **filters,
git_repository *repo,
git_blob *blob, /* can be NULL */
const char *path,
git_filter_mode_t mode,
git_filter_options *filter_opts);
/*
* Available filters
*/
extern git_filter *git_crlf_filter_new(void);
extern git_filter *git_ident_filter_new(void);
#endif
+1188
View File
File diff suppressed because it is too large Load Diff
+390
View File
@@ -0,0 +1,390 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_futils_h__
#define INCLUDE_futils_h__
#include "common.h"
#include "map.h"
#include "posix.h"
#include "path.h"
#include "pool.h"
#include "strmap.h"
#include "oid.h"
/**
* 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, git_oid *checksum, int *updated);
extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len);
/* Additional constants for `git_futils_writebuffer`'s `open_flags`. We
* support these internally and they will be removed before the `open` call.
*/
#ifndef O_FSYNC
# define O_FSYNC (1 << 31)
#endif
extern int git_futils_writebuffer(
const git_buf *buf, const char *path, int open_flags, mode_t mode);
/**
* 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.
*/
/**
* 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);
/**
* Create and open a process-locked file
*/
extern int git_futils_creat_locked(const char *path, const mode_t mode);
/**
* Create and 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);
/**
* Create a path recursively.
*/
extern int git_futils_mkdir_r(const char *path, const mode_t mode);
/**
* Flags to pass to `git_futils_mkdir`.
*
* * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists.
* * GIT_MKDIR_PATH says to make all components in the path.
* * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation
* * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path
* * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path
* * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path
* * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST
* * GIT_MKDIR_REMOVE_FILES says to remove files and recreate dirs
* * GIT_MKDIR_REMOVE_SYMLINKS says to remove symlinks and recreate dirs
*
* Note that the chmod options will be executed even if the directory already
* exists, unless GIT_MKDIR_EXCL is given.
*/
typedef enum {
GIT_MKDIR_EXCL = 1,
GIT_MKDIR_PATH = 2,
GIT_MKDIR_CHMOD = 4,
GIT_MKDIR_CHMOD_PATH = 8,
GIT_MKDIR_SKIP_LAST = 16,
GIT_MKDIR_SKIP_LAST2 = 32,
GIT_MKDIR_VERIFY_DIR = 64,
GIT_MKDIR_REMOVE_FILES = 128,
GIT_MKDIR_REMOVE_SYMLINKS = 256,
} git_futils_mkdir_flags;
struct git_futils_mkdir_perfdata
{
size_t stat_calls;
size_t mkdir_calls;
size_t chmod_calls;
};
struct git_futils_mkdir_options
{
git_strmap *dir_map;
git_pool *pool;
struct git_futils_mkdir_perfdata perfdata;
};
/**
* Create a directory or entire path.
*
* This makes a directory (and the entire path leading up to it if requested),
* and optionally chmods the directory immediately after (or each part of the
* path if requested).
*
* @param path The path to create, relative to base.
* @param base Root for relative path. These directories will never be made.
* @param mode The mode to use for created directories.
* @param flags Combination of the mkdir flags above.
* @param opts Extended options, or null.
* @return 0 on success, else error code
*/
extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts);
/**
* Create a directory or entire path. Similar to `git_futils_mkdir_relative`
* without performance data.
*/
extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags);
/**
* 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);
/**
* Flags to pass to `git_futils_rmdir_r`.
*
* * GIT_RMDIR_EMPTY_HIERARCHY - the default; remove hierarchy of empty
* dirs and generate error if any files are found.
* * GIT_RMDIR_REMOVE_FILES - attempt to remove files in the hierarchy.
* * GIT_RMDIR_SKIP_NONEMPTY - skip non-empty directories with no error.
* * GIT_RMDIR_EMPTY_PARENTS - remove containing directories up to base
* if removing this item leaves them empty
* * GIT_RMDIR_REMOVE_BLOCKERS - remove blocking file that causes ENOTDIR
* * GIT_RMDIR_SKIP_ROOT - don't remove root directory itself
*/
typedef enum {
GIT_RMDIR_EMPTY_HIERARCHY = 0,
GIT_RMDIR_REMOVE_FILES = (1 << 0),
GIT_RMDIR_SKIP_NONEMPTY = (1 << 1),
GIT_RMDIR_EMPTY_PARENTS = (1 << 2),
GIT_RMDIR_REMOVE_BLOCKERS = (1 << 3),
GIT_RMDIR_SKIP_ROOT = (1 << 4),
} git_futils_rmdir_flags;
/**
* Remove path and any files and directories beneath it.
*
* @param path Path to the top level directory to process.
* @param base Root for relative path.
* @param flags Combination of git_futils_rmdir_flags values
* @return 0 on success; -1 on error.
*/
extern int git_futils_rmdir_r(const char *path, const char *base, uint32_t flags);
/**
* 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, mode_t mode);
/**
* 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);
/**
* Copy a file
*
* The filemode will be used for the newly created file.
*/
extern int git_futils_cp(
const char *from,
const char *to,
mode_t filemode);
/**
* Set the files atime and mtime to the given time, or the current time
* if `ts` is NULL.
*/
extern int git_futils_touch(const char *path, time_t *when);
/**
* Flags that can be passed to `git_futils_cp_r`.
*
* - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no
* files under them (otherwise directories will only be created lazily
* when a file inside them is copied).
* - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored.
* - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored.
* - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content,
* otherwise they are silently skipped.
* - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode`
* - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the
* source file to the target; with this flag, always use 0666 (or 0777 if
* source has exec bits set) for target.
* - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files
*/
typedef enum {
GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0),
GIT_CPDIR_COPY_SYMLINKS = (1u << 1),
GIT_CPDIR_COPY_DOTFILES = (1u << 2),
GIT_CPDIR_OVERWRITE = (1u << 3),
GIT_CPDIR_CHMOD_DIRS = (1u << 4),
GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5),
GIT_CPDIR_LINK_FILES = (1u << 6),
} git_futils_cpdir_flags;
/**
* Copy a directory tree.
*
* This copies directories and files from one root to another. You can
* pass a combinationof GIT_CPDIR flags as defined above.
*
* If you pass the CHMOD flag, then the dirmode will be applied to all
* directories that are created during the copy, overiding the natural
* permissions. If you do not pass the CHMOD flag, then the dirmode
* will actually be copied from the source files and the `dirmode` arg
* will be ignored.
*/
extern int git_futils_cp_r(
const char *from,
const char *to,
uint32_t flags,
mode_t dirmode);
/**
* Open a file readonly and set error if needed.
*/
extern int git_futils_open_ro(const char *path);
/**
* Truncate a file, creating it if it doesn't exist.
*/
extern int git_futils_truncate(const char *path, int mode);
/**
* Get the filesize in bytes of a file
*/
extern int git_futils_filesize(uint64_t *out, git_file fd);
#define GIT_PERMS_IS_EXEC(MODE) (((MODE) & 0111) != 0)
#define GIT_PERMS_CANONICAL(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0755 : 0644)
#define GIT_PERMS_FOR_WRITE(MODE) (GIT_PERMS_IS_EXEC(MODE) ? 0777 : 0666)
#define GIT_MODE_PERMS_MASK 0777
#define GIT_MODE_TYPE_MASK 0170000
#define GIT_MODE_TYPE(MODE) ((MODE) & GIT_MODE_TYPE_MASK)
#define GIT_MODE_ISBLOB(MODE) (GIT_MODE_TYPE(MODE) == GIT_MODE_TYPE(GIT_FILEMODE_BLOB))
/**
* 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);
/**
* Read-only map all or part of a file into memory.
* When possible this function should favor a virtual memory
* style mapping over some form of malloc()+read(), as the
* data access will be random and is not likely to touch the
* majority of the region requested.
*
* @param out buffer to populate with the mapping information.
* @param fd open descriptor to configure the mapping from.
* @param begin first byte to map, this should be page aligned.
* @param len number of bytes to map.
* @return
* - 0 on success;
* - -1 on error.
*/
extern int git_futils_mmap_ro(
git_map *out,
git_file fd,
off64_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 git_futils_mmap_free(git_map *map);
/**
* Create a "fake" symlink (text file containing the target path).
*
* @param new symlink file to be created
* @param old original symlink target
* @return 0 on success, -1 on error
*/
extern int git_futils_fake_symlink(const char *new, const char *old);
/**
* A file stamp represents a snapshot of information about a file that can
* be used to test if the file changes. This portable implementation is
* based on stat data about that file, but it is possible that OS specific
* versions could be implemented in the future.
*/
typedef struct {
struct timespec mtime;
uint64_t size;
unsigned int ino;
} git_futils_filestamp;
/**
* Compare stat information for file with reference info.
*
* This function updates the file stamp to current data for the given path
* and returns 0 if the file is up-to-date relative to the prior setting,
* 1 if the file has been changed, or GIT_ENOTFOUND if the file doesn't
* exist. This will not call git_error_set, so you must set the error if you
* plan to return an error.
*
* @param stamp File stamp to be checked
* @param path Path to stat and check if changed
* @return 0 if up-to-date, 1 if out-of-date, GIT_ENOTFOUND if cannot stat
*/
extern int git_futils_filestamp_check(
git_futils_filestamp *stamp, const char *path);
/**
* Set or reset file stamp data
*
* This writes the target file stamp. If the source is NULL, this will set
* the target stamp to values that will definitely be out of date. If the
* source is not NULL, this copies the source values to the target.
*
* @param tgt File stamp to write to
* @param src File stamp to copy from or NULL to clear the target
*/
extern void git_futils_filestamp_set(
git_futils_filestamp *tgt, const git_futils_filestamp *src);
/**
* Set file stamp data from stat structure
*/
extern void git_futils_filestamp_set_from_stat(
git_futils_filestamp *stamp, struct stat *st);
/**
* `fsync` the parent directory of the given path, if `fsync` is
* supported for directories on this platform.
*
* @param path Path of the directory to sync.
* @return 0 on success, -1 on error
*/
extern int git_futils_fsync_dir(const char *path);
/**
* `fsync` the parent directory of the given path, if `fsync` is
* supported for directories on this platform.
*
* @param path Path of the file whose parent directory should be synced.
* @return 0 on success, -1 on error
*/
extern int git_futils_fsync_parent(const char *path);
#endif
+361
View File
@@ -0,0 +1,361 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "global.h"
#include "alloc.h"
#include "hash.h"
#include "sysdir.h"
#include "filter.h"
#include "merge_driver.h"
#include "streams/registry.h"
#include "streams/mbedtls.h"
#include "streams/openssl.h"
#include "thread-utils.h"
#include "git2/global.h"
#include "transports/ssh.h"
#if defined(GIT_MSVC_CRTDBG)
#include "win32/w32_stack.h"
#include "win32/w32_crtdbg_stacktrace.h"
#endif
git_mutex git__mwindow_mutex;
typedef int (*git_global_init_fn)(void);
static git_global_init_fn git__init_callbacks[] = {
git_allocator_global_init,
git_hash_global_init,
git_sysdir_global_init,
git_filter_global_init,
git_merge_driver_global_init,
git_transport_ssh_global_init,
git_stream_registry_global_init,
git_openssl_stream_global_init,
git_mbedtls_stream_global_init,
git_mwindow_global_init
};
static git_global_shutdown_fn git__shutdown_callbacks[ARRAY_SIZE(git__init_callbacks)];
static git_atomic git__n_shutdown_callbacks;
static git_atomic git__n_inits;
char *git__user_agent;
char *git__ssl_ciphers;
void git__on_shutdown(git_global_shutdown_fn callback)
{
int count = git_atomic_inc(&git__n_shutdown_callbacks);
assert(count <= (int) ARRAY_SIZE(git__shutdown_callbacks) && count > 0);
git__shutdown_callbacks[count - 1] = callback;
}
static void git__global_state_cleanup(git_global_st *st)
{
if (!st)
return;
git__free(st->error_t.message);
st->error_t.message = NULL;
}
static int init_common(void)
{
size_t i;
int ret;
/* Initialize the CRT debug allocator first, before our first malloc */
#if defined(GIT_MSVC_CRTDBG)
git_win32__crtdbg_stacktrace_init();
git_win32__stack_init();
#endif
/* Initialize subsystems that have global state */
for (i = 0; i < ARRAY_SIZE(git__init_callbacks); i++)
if ((ret = git__init_callbacks[i]()) != 0)
break;
GIT_MEMORY_BARRIER;
return ret;
}
static void shutdown_common(void)
{
int pos;
/* Shutdown subsystems that have registered */
for (pos = git_atomic_get(&git__n_shutdown_callbacks);
pos > 0;
pos = git_atomic_dec(&git__n_shutdown_callbacks)) {
git_global_shutdown_fn cb = git__swap(
git__shutdown_callbacks[pos - 1], NULL);
if (cb != NULL)
cb();
}
git__free(git__user_agent);
git__free(git__ssl_ciphers);
}
/**
* Handle the global state with TLS
*
* If libgit2 is built with GIT_THREADS enabled,
* the `git_libgit2_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_libgit2_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.
*/
/*
* `git_libgit2_init()` allows subsystems to perform global setup,
* which may take place in the global scope. An explicit memory
* fence exists at the exit of `git_libgit2_init()`. Without this,
* CPU cores are free to reorder cache invalidation of `_tls_init`
* before cache invalidation of the subsystems' newly written global
* state.
*/
#if defined(GIT_THREADS) && defined(GIT_WIN32)
static DWORD _fls_index;
static volatile LONG _mutex = 0;
static void WINAPI fls_free(void *st)
{
git__global_state_cleanup(st);
git__free(st);
}
static int synchronized_threads_init(void)
{
int error;
if ((_fls_index = FlsAlloc(fls_free)) == FLS_OUT_OF_INDEXES)
return -1;
git_threads_init();
if (git_mutex_init(&git__mwindow_mutex))
return -1;
error = init_common();
return error;
}
int git_libgit2_init(void)
{
int ret;
/* Enter the lock */
while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
/* Only do work on a 0 -> 1 transition of the refcount */
if ((ret = git_atomic_inc(&git__n_inits)) == 1) {
if (synchronized_threads_init() < 0)
ret = -1;
}
/* Exit the lock */
InterlockedExchange(&_mutex, 0);
return ret;
}
int git_libgit2_shutdown(void)
{
int ret;
/* Enter the lock */
while (InterlockedCompareExchange(&_mutex, 1, 0)) { Sleep(0); }
/* Only do work on a 1 -> 0 transition of the refcount */
if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
shutdown_common();
FlsFree(_fls_index);
git_mutex_free(&git__mwindow_mutex);
#if defined(GIT_MSVC_CRTDBG)
git_win32__crtdbg_stacktrace_cleanup();
git_win32__stack_cleanup();
#endif
}
/* Exit the lock */
InterlockedExchange(&_mutex, 0);
return ret;
}
git_global_st *git__global_state(void)
{
git_global_st *ptr;
assert(git_atomic_get(&git__n_inits) > 0);
if ((ptr = FlsGetValue(_fls_index)) != NULL)
return ptr;
ptr = git__calloc(1, sizeof(git_global_st));
if (!ptr)
return NULL;
git_buf_init(&ptr->error_buf, 0);
FlsSetValue(_fls_index, ptr);
return ptr;
}
#elif defined(GIT_THREADS) && defined(_POSIX_THREADS)
static pthread_key_t _tls_key;
static pthread_mutex_t _init_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t _once_init = PTHREAD_ONCE_INIT;
int init_error = 0;
static void cb__free_status(void *st)
{
git__global_state_cleanup(st);
git__free(st);
}
static void init_once(void)
{
if ((init_error = git_mutex_init(&git__mwindow_mutex)) != 0)
return;
pthread_key_create(&_tls_key, &cb__free_status);
init_error = init_common();
}
int git_libgit2_init(void)
{
int ret, err;
if ((err = pthread_mutex_lock(&_init_mutex)) != 0)
return err;
ret = git_atomic_inc(&git__n_inits);
err = pthread_once(&_once_init, init_once);
err |= pthread_mutex_unlock(&_init_mutex);
if (err || init_error)
return err | init_error;
return ret;
}
int git_libgit2_shutdown(void)
{
void *ptr = NULL;
pthread_once_t new_once = PTHREAD_ONCE_INIT;
int error, ret;
if ((error = pthread_mutex_lock(&_init_mutex)) != 0)
return error;
if ((ret = git_atomic_dec(&git__n_inits)) != 0)
goto out;
/* Shut down any subsystems that have global state */
shutdown_common();
ptr = pthread_getspecific(_tls_key);
pthread_setspecific(_tls_key, NULL);
git__global_state_cleanup(ptr);
git__free(ptr);
pthread_key_delete(_tls_key);
git_mutex_free(&git__mwindow_mutex);
_once_init = new_once;
out:
if ((error = pthread_mutex_unlock(&_init_mutex)) != 0)
return error;
return ret;
}
git_global_st *git__global_state(void)
{
git_global_st *ptr;
assert(git_atomic_get(&git__n_inits) > 0);
if ((ptr = pthread_getspecific(_tls_key)) != NULL)
return ptr;
ptr = git__calloc(1, sizeof(git_global_st));
if (!ptr)
return NULL;
git_buf_init(&ptr->error_buf, 0);
pthread_setspecific(_tls_key, ptr);
return ptr;
}
#else
static git_global_st __state;
int git_libgit2_init(void)
{
int ret;
/* Only init subsystems the first time */
if ((ret = git_atomic_inc(&git__n_inits)) != 1)
return ret;
if ((ret = init_common()) < 0)
return ret;
return 1;
}
int git_libgit2_shutdown(void)
{
int ret;
/* Shut down any subsystems that have global state */
if ((ret = git_atomic_dec(&git__n_inits)) == 0) {
shutdown_common();
git__global_state_cleanup(&__state);
memset(&__state, 0, sizeof(__state));
}
return ret;
}
git_global_st *git__global_state(void)
{
return &__state;
}
#endif /* GIT_THREADS */
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_global_h__
#define INCLUDE_global_h__
#include "common.h"
#include "mwindow.h"
#include "hash.h"
typedef struct {
git_error *last_error;
git_error error_t;
git_buf error_buf;
char oid_fmt[GIT_OID_HEXSZ+1];
/* On Windows, this is the current child thread that was started by
* `git_thread_create`. This is used to set the thread's exit code
* when terminated by `git_thread_exit`. It is unused on POSIX.
*/
git_thread *current_thread;
} git_global_st;
git_global_st *git__global_state(void);
extern git_mutex git__mwindow_mutex;
#define GIT_GLOBAL (git__global_state())
typedef void (*git_global_shutdown_fn)(void);
extern void git__on_shutdown(git_global_shutdown_fn callback);
extern const char *git_libgit2__user_agent(void);
extern const char *git_libgit2__ssl_ciphers(void);
#endif
+194
View File
@@ -0,0 +1,194 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common.h"
#include "revwalk.h"
#include "merge.h"
#include "git2/graph.h"
static int interesting(git_pqueue *list, git_commit_list *roots)
{
unsigned int i;
for (i = 0; i < git_pqueue_size(list); i++) {
git_commit_list_node *commit = git_pqueue_get(list, i);
if ((commit->flags & STALE) == 0)
return 1;
}
while(roots) {
if ((roots->item->flags & STALE) == 0)
return 1;
roots = roots->next;
}
return 0;
}
static int mark_parents(git_revwalk *walk, git_commit_list_node *one,
git_commit_list_node *two)
{
unsigned int i;
git_commit_list *roots = NULL;
git_pqueue list;
/* if the commit is repeated, we have a our merge base already */
if (one == two) {
one->flags |= PARENT1 | PARENT2 | RESULT;
return 0;
}
if (git_pqueue_init(&list, 0, 2, git_commit_list_time_cmp) < 0)
return -1;
if (git_commit_list_parse(walk, one) < 0)
goto on_error;
one->flags |= PARENT1;
if (git_pqueue_insert(&list, one) < 0)
goto on_error;
if (git_commit_list_parse(walk, two) < 0)
goto on_error;
two->flags |= PARENT2;
if (git_pqueue_insert(&list, two) < 0)
goto on_error;
/* as long as there are non-STALE commits */
while (interesting(&list, roots)) {
git_commit_list_node *commit = git_pqueue_pop(&list);
unsigned int flags;
if (commit == NULL)
break;
flags = commit->flags & (PARENT1 | PARENT2 | STALE);
if (flags == (PARENT1 | PARENT2)) {
if (!(commit->flags & RESULT))
commit->flags |= RESULT;
/* we mark the parents of a merge stale */
flags |= STALE;
}
for (i = 0; i < commit->out_degree; i++) {
git_commit_list_node *p = commit->parents[i];
if ((p->flags & flags) == flags)
continue;
if (git_commit_list_parse(walk, p) < 0)
goto on_error;
p->flags |= flags;
if (git_pqueue_insert(&list, p) < 0)
goto on_error;
}
/* Keep track of root commits, to make sure the path gets marked */
if (commit->out_degree == 0) {
if (git_commit_list_insert(commit, &roots) == NULL)
goto on_error;
}
}
git_commit_list_free(&roots);
git_pqueue_free(&list);
return 0;
on_error:
git_commit_list_free(&roots);
git_pqueue_free(&list);
return -1;
}
static int ahead_behind(git_commit_list_node *one, git_commit_list_node *two,
size_t *ahead, size_t *behind)
{
git_commit_list_node *commit;
git_pqueue pq;
int error = 0, i;
*ahead = 0;
*behind = 0;
if (git_pqueue_init(&pq, 0, 2, git_commit_list_time_cmp) < 0)
return -1;
if ((error = git_pqueue_insert(&pq, one)) < 0 ||
(error = git_pqueue_insert(&pq, two)) < 0)
goto done;
while ((commit = git_pqueue_pop(&pq)) != NULL) {
if (commit->flags & RESULT ||
(commit->flags & (PARENT1 | PARENT2)) == (PARENT1 | PARENT2))
continue;
else if (commit->flags & PARENT1)
(*ahead)++;
else if (commit->flags & PARENT2)
(*behind)++;
for (i = 0; i < commit->out_degree; i++) {
git_commit_list_node *p = commit->parents[i];
if ((error = git_pqueue_insert(&pq, p)) < 0)
goto done;
}
commit->flags |= RESULT;
}
done:
git_pqueue_free(&pq);
return error;
}
int git_graph_ahead_behind(size_t *ahead, size_t *behind, git_repository *repo,
const git_oid *local, const git_oid *upstream)
{
git_revwalk *walk;
git_commit_list_node *commit_u, *commit_l;
if (git_revwalk_new(&walk, repo) < 0)
return -1;
commit_u = git_revwalk__commit_lookup(walk, upstream);
if (commit_u == NULL)
goto on_error;
commit_l = git_revwalk__commit_lookup(walk, local);
if (commit_l == NULL)
goto on_error;
if (mark_parents(walk, commit_l, commit_u) < 0)
goto on_error;
if (ahead_behind(commit_l, commit_u, ahead, behind) < 0)
goto on_error;
git_revwalk_free(walk);
return 0;
on_error:
git_revwalk_free(walk);
return -1;
}
int git_graph_descendant_of(git_repository *repo, const git_oid *commit, const git_oid *ancestor)
{
git_oid merge_base;
int error;
if (git_oid_equal(commit, ancestor))
return 0;
error = git_merge_base(&merge_base, repo, commit, ancestor);
/* No merge-base found, it's not a descendant */
if (error == GIT_ENOTFOUND)
return 0;
if (error < 0)
return error;
return git_oid_equal(&merge_base, ancestor);
}
+107
View File
@@ -0,0 +1,107 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "hash.h"
int git_hash_global_init(void)
{
return git_hash_sha1_global_init();
}
int git_hash_ctx_init(git_hash_ctx *ctx)
{
int error;
if ((error = git_hash_sha1_ctx_init(&ctx->sha1)) < 0)
return error;
ctx->algo = GIT_HASH_ALGO_SHA1;
return 0;
}
void git_hash_ctx_cleanup(git_hash_ctx *ctx)
{
switch (ctx->algo) {
case GIT_HASH_ALGO_SHA1:
git_hash_sha1_ctx_cleanup(&ctx->sha1);
return;
default:
assert(0);
}
}
int git_hash_init(git_hash_ctx *ctx)
{
switch (ctx->algo) {
case GIT_HASH_ALGO_SHA1:
return git_hash_sha1_init(&ctx->sha1);
default:
assert(0);
return -1;
}
}
int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len)
{
switch (ctx->algo) {
case GIT_HASH_ALGO_SHA1:
return git_hash_sha1_update(&ctx->sha1, data, len);
default:
assert(0);
return -1;
}
}
int git_hash_final(git_oid *out, git_hash_ctx *ctx)
{
switch (ctx->algo) {
case GIT_HASH_ALGO_SHA1:
return git_hash_sha1_final(out, &ctx->sha1);
default:
assert(0);
return -1;
}
}
int git_hash_buf(git_oid *out, const void *data, size_t len)
{
git_hash_ctx ctx;
int error = 0;
if (git_hash_ctx_init(&ctx) < 0)
return -1;
if ((error = git_hash_update(&ctx, data, len)) >= 0)
error = git_hash_final(out, &ctx);
git_hash_ctx_cleanup(&ctx);
return error;
}
int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n)
{
git_hash_ctx ctx;
size_t i;
int error = 0;
if (git_hash_ctx_init(&ctx) < 0)
return -1;
for (i = 0; i < n; i++) {
if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0)
goto done;
}
error = git_hash_final(out, &ctx);
done:
git_hash_ctx_cleanup(&ctx);
return error;
}
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_hash_h__
#define INCLUDE_hash_h__
#include "common.h"
#include "git2/oid.h"
typedef struct {
void *data;
size_t len;
} git_buf_vec;
typedef enum {
GIT_HASH_ALGO_UNKNOWN = 0,
GIT_HASH_ALGO_SHA1,
} git_hash_algo_t;
#include "hash/sha1.h"
typedef struct git_hash_ctx {
union {
git_hash_sha1_ctx sha1;
};
git_hash_algo_t algo;
} git_hash_ctx;
int git_hash_global_init(void);
int git_hash_ctx_init(git_hash_ctx *ctx);
void git_hash_ctx_cleanup(git_hash_ctx *ctx);
int git_hash_init(git_hash_ctx *c);
int git_hash_update(git_hash_ctx *c, const void *data, size_t len);
int git_hash_final(git_oid *out, git_hash_ctx *c);
int git_hash_buf(git_oid *out, const void *data, size_t len);
int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n);
#endif
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_hash_sha1_h__
#define INCLUDE_hash_sha1_h__
#include "common.h"
typedef struct git_hash_sha1_ctx git_hash_sha1_ctx;
#if defined(GIT_SHA1_COLLISIONDETECT)
# include "sha1/collisiondetect.h"
#elif defined(GIT_SHA1_COMMON_CRYPTO)
# include "sha1/common_crypto.h"
#elif defined(GIT_SHA1_OPENSSL)
# include "sha1/openssl.h"
#elif defined(GIT_SHA1_WIN32)
# include "sha1/win32.h"
#elif defined(GIT_SHA1_MBEDTLS)
# include "sha1/mbedtls.h"
#else
# include "sha1/generic.h"
#endif
int git_hash_sha1_global_init(void);
int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx);
void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx);
int git_hash_sha1_init(git_hash_sha1_ctx *c);
int git_hash_sha1_update(git_hash_sha1_ctx *c, const void *data, size_t len);
int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *c);
#endif
+48
View File
@@ -0,0 +1,48 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "collisiondetect.h"
int git_hash_sha1_global_init(void)
{
return 0;
}
int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
{
return git_hash_sha1_init(ctx);
}
void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
{
GIT_UNUSED(ctx);
}
int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
{
assert(ctx);
SHA1DCInit(&ctx->c);
return 0;
}
int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
{
assert(ctx);
SHA1DCUpdate(&ctx->c, data, len);
return 0;
}
int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx)
{
assert(ctx);
if (SHA1DCFinal(out->id, &ctx->c)) {
git_error_set(GIT_ERROR_SHA1, "SHA1 collision attack detected");
return -1;
}
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_hash_sha1_collisiondetect_h__
#define INCLUDE_hash_sha1_collisiondetect_h__
#include "hash/sha1.h"
#include "sha1dc/sha1.h"
struct git_hash_sha1_ctx {
SHA1_CTX c;
};
#endif
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "common_crypto.h"
#define CC_LONG_MAX ((CC_LONG)-1)
int git_hash_sha1_global_init(void)
{
return 0;
}
int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
{
return git_hash_sha1_init(ctx);
}
void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
{
GIT_UNUSED(ctx);
}
int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
{
assert(ctx);
CC_SHA1_Init(&ctx->c);
return 0;
}
int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *_data, size_t len)
{
const unsigned char *data = _data;
assert(ctx);
while (len > 0) {
CC_LONG chunk = (len > CC_LONG_MAX) ? CC_LONG_MAX : (CC_LONG)len;
CC_SHA1_Update(&ctx->c, data, chunk);
data += chunk;
len -= chunk;
}
return 0;
}
int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx)
{
assert(ctx);
CC_SHA1_Final(out->id, &ctx->c);
return 0;
}
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#ifndef INCLUDE_hash_sha1_common_crypto_h__
#define INCLUDE_hash_sha1_common_crypto_h__
#include "hash/sha1.h"
#include <CommonCrypto/CommonDigest.h>
struct git_hash_sha1_ctx {
CC_SHA1_CTX c;
};
#endif
+300
View File
@@ -0,0 +1,300 @@
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "generic.h"
#if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
/*
* Force usage of rol or ror by selecting the one with the smaller constant.
* It _can_ generate slightly smaller code (a constant of 1 is special), but
* perhaps more importantly it's possibly faster on any uarch that does a
* rotate with a loop.
*/
#define SHA_ASM(op, x, n) (__extension__ ({ unsigned int __res; __asm__(op " %1,%0":"=r" (__res):"i" (n), "0" (x)); __res; }))
#define SHA_ROL(x,n) SHA_ASM("rol", x, n)
#define SHA_ROR(x,n) SHA_ASM("ror", x, n)
#else
#define SHA_ROT(X,l,r) (((X) << (l)) | ((X) >> (r)))
#define SHA_ROL(X,n) SHA_ROT(X,n,32-(n))
#define SHA_ROR(X,n) SHA_ROT(X,32-(n),n)
#endif
/*
* If you have 32 registers or more, the compiler can (and should)
* try to change the array[] accesses into registers. However, on
* machines with less than ~25 registers, that won't really work,
* and at least gcc will make an unholy mess of it.
*
* So to avoid that mess which just slows things down, we force
* the stores to memory to actually happen (we might be better off
* with a 'W(t)=(val);asm("":"+m" (W(t))' there instead, as
* suggested by Artur Skawina - that will also make gcc unable to
* try to do the silly "optimize away loads" part because it won't
* see what the value will be).
*
* Ben Herrenschmidt reports that on PPC, the C version comes close
* to the optimized asm with this (ie on PPC you don't want that
* 'volatile', since there are lots of registers).
*
* On ARM we get the best code generation by forcing a full memory barrier
* between each SHA_ROUND, otherwise gcc happily get wild with spilling and
* the stack frame size simply explode and performance goes down the drain.
*/
#if defined(__i386__) || defined(__x86_64__)
#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)
#else
#define setW(x, val) (W(x) = (val))
#endif
/*
* Performance might be improved if the CPU architecture is OK with
* unaligned 32-bit loads and a fast ntohl() is available.
* Otherwise fall back to byte loads and shifts which is portable,
* and is faster on architectures with memory alignment issues.
*/
#if defined(__i386__) || defined(__x86_64__) || \
defined(_M_IX86) || defined(_M_X64) || \
defined(__ppc__) || defined(__ppc64__) || \
defined(__powerpc__) || defined(__powerpc64__) || \
defined(__s390__) || defined(__s390x__)
#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) ( \
(*((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)
#endif
/* This "rolls" over the 512-bit array */
#define W(x) (array[(x)&15])
/*
* Where do we get the source from? The first 16 iterations get it from
* the input data, the next mix it from the 512-bit array.
*/
#define SHA_SRC(t) get_be32(data + t)
#define SHA_MIX(t) SHA_ROL(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)
#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \
unsigned int TEMP = input(t); setW(t, TEMP); \
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_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 )
static void hash__block(git_hash_sha1_ctx *ctx, const unsigned int *data)
{
unsigned int A,B,C,D,E;
unsigned int array[16];
A = ctx->H[0];
B = ctx->H[1];
C = ctx->H[2];
D = ctx->H[3];
E = ctx->H[4];
/* Round 1 - iterations 0-16 take their input from 'data' */
T_0_15( 0, A, B, C, D, E);
T_0_15( 1, E, A, B, C, D);
T_0_15( 2, D, E, A, B, C);
T_0_15( 3, C, D, E, A, B);
T_0_15( 4, B, C, D, E, A);
T_0_15( 5, A, B, C, D, E);
T_0_15( 6, E, A, B, C, D);
T_0_15( 7, D, E, A, B, C);
T_0_15( 8, C, D, E, A, B);
T_0_15( 9, B, C, D, E, A);
T_0_15(10, A, B, C, D, E);
T_0_15(11, E, A, B, C, D);
T_0_15(12, D, E, A, B, C);
T_0_15(13, C, D, E, A, B);
T_0_15(14, B, C, D, E, A);
T_0_15(15, A, B, C, D, E);
/* Round 1 - tail. Input from 512-bit mixing array */
T_16_19(16, E, A, B, C, D);
T_16_19(17, D, E, A, B, C);
T_16_19(18, C, D, E, A, B);
T_16_19(19, B, C, D, E, A);
/* Round 2 */
T_20_39(20, A, B, C, D, E);
T_20_39(21, E, A, B, C, D);
T_20_39(22, D, E, A, B, C);
T_20_39(23, C, D, E, A, B);
T_20_39(24, B, C, D, E, A);
T_20_39(25, A, B, C, D, E);
T_20_39(26, E, A, B, C, D);
T_20_39(27, D, E, A, B, C);
T_20_39(28, C, D, E, A, B);
T_20_39(29, B, C, D, E, A);
T_20_39(30, A, B, C, D, E);
T_20_39(31, E, A, B, C, D);
T_20_39(32, D, E, A, B, C);
T_20_39(33, C, D, E, A, B);
T_20_39(34, B, C, D, E, A);
T_20_39(35, A, B, C, D, E);
T_20_39(36, E, A, B, C, D);
T_20_39(37, D, E, A, B, C);
T_20_39(38, C, D, E, A, B);
T_20_39(39, B, C, D, E, A);
/* Round 3 */
T_40_59(40, A, B, C, D, E);
T_40_59(41, E, A, B, C, D);
T_40_59(42, D, E, A, B, C);
T_40_59(43, C, D, E, A, B);
T_40_59(44, B, C, D, E, A);
T_40_59(45, A, B, C, D, E);
T_40_59(46, E, A, B, C, D);
T_40_59(47, D, E, A, B, C);
T_40_59(48, C, D, E, A, B);
T_40_59(49, B, C, D, E, A);
T_40_59(50, A, B, C, D, E);
T_40_59(51, E, A, B, C, D);
T_40_59(52, D, E, A, B, C);
T_40_59(53, C, D, E, A, B);
T_40_59(54, B, C, D, E, A);
T_40_59(55, A, B, C, D, E);
T_40_59(56, E, A, B, C, D);
T_40_59(57, D, E, A, B, C);
T_40_59(58, C, D, E, A, B);
T_40_59(59, B, C, D, E, A);
/* Round 4 */
T_60_79(60, A, B, C, D, E);
T_60_79(61, E, A, B, C, D);
T_60_79(62, D, E, A, B, C);
T_60_79(63, C, D, E, A, B);
T_60_79(64, B, C, D, E, A);
T_60_79(65, A, B, C, D, E);
T_60_79(66, E, A, B, C, D);
T_60_79(67, D, E, A, B, C);
T_60_79(68, C, D, E, A, B);
T_60_79(69, B, C, D, E, A);
T_60_79(70, A, B, C, D, E);
T_60_79(71, E, A, B, C, D);
T_60_79(72, D, E, A, B, C);
T_60_79(73, C, D, E, A, B);
T_60_79(74, B, C, D, E, A);
T_60_79(75, A, B, C, D, E);
T_60_79(76, E, A, B, C, D);
T_60_79(77, D, E, A, B, C);
T_60_79(78, C, D, E, A, B);
T_60_79(79, B, C, D, E, A);
ctx->H[0] += A;
ctx->H[1] += B;
ctx->H[2] += C;
ctx->H[3] += D;
ctx->H[4] += E;
}
int git_hash_sha1_global_init(void)
{
return 0;
}
int git_hash_sha1_ctx_init(git_hash_sha1_ctx *ctx)
{
return git_hash_sha1_init(ctx);
}
void git_hash_sha1_ctx_cleanup(git_hash_sha1_ctx *ctx)
{
GIT_UNUSED(ctx);
}
int git_hash_sha1_init(git_hash_sha1_ctx *ctx)
{
ctx->size = 0;
/* Initialize H with the magic constants (see FIPS180 for constants) */
ctx->H[0] = 0x67452301;
ctx->H[1] = 0xefcdab89;
ctx->H[2] = 0x98badcfe;
ctx->H[3] = 0x10325476;
ctx->H[4] = 0xc3d2e1f0;
return 0;
}
int git_hash_sha1_update(git_hash_sha1_ctx *ctx, const void *data, size_t len)
{
unsigned int lenW = ctx->size & 63;
ctx->size += len;
/* Read the data into W and process blocks as they get full */
if (lenW) {
unsigned int left = 64 - lenW;
if (len < left)
left = (unsigned int)len;
memcpy(lenW + (char *)ctx->W, data, left);
lenW = (lenW + left) & 63;
len -= left;
data = ((const char *)data + left);
if (lenW)
return 0;
hash__block(ctx, ctx->W);
}
while (len >= 64) {
hash__block(ctx, data);
data = ((const char *)data + 64);
len -= 64;
}
if (len)
memcpy(ctx->W, data, len);
return 0;
}
int git_hash_sha1_final(git_oid *out, git_hash_sha1_ctx *ctx)
{
static const unsigned char pad[64] = { 0x80 };
unsigned int padlen[2];
int i;
/* Pad with a binary 1 (ie 0x80), then zeroes, then length */
padlen[0] = htonl((uint32_t)(ctx->size >> 29));
padlen[1] = htonl((uint32_t)(ctx->size << 3));
i = ctx->size & 63;
git_hash_sha1_update(ctx, pad, 1+ (63 & (55 - i)));
git_hash_sha1_update(ctx, padlen, 8);
/* Output hash */
for (i = 0; i < 5; i++)
put_be32(out->id + i*4, ctx->H[i]);
return 0;
}

Some files were not shown because too many files have changed in this diff Show More