diff options
320 files changed, 15308 insertions, 4820 deletions
diff --git a/.travis.yml b/.travis.yml index e8f37d229..256227589 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,8 +46,3 @@ notifications: - irc.freenode.net#libgit2 on_success: change on_failure: always - recipients: - - vicent@github.com - email: - on_success: change - on_failure: always @@ -4,6 +4,7 @@ to the libgit2 project (sorted alphabetically): Alex Budovski Alexei Sholik Andreas Ericsson +Anton "antong" Gyllenberg Ankur Sethi Ben Noordhuis Ben Straub @@ -32,12 +33,15 @@ Jonathan "Duke" Leto Julien Miotte Julio Espinoza-Sokal Justin Love +Kelly "kelly.leahy" Leahy Kirill A. Shutemov Lambert CLARA Luc Bertrand Marc Pegon Marcel Groothuis Marco Villegas +Michael "schu" Schubert +Microsoft Corporation Olivier Ramonat Peter Drahoš Pierre Habouzit @@ -48,8 +52,9 @@ Romain Geissler Romain Muller Russell Belfer Sakari Jokinen -Sam +Samuel Charles "Sam" Day Sarath Lakshman +Sascha Cunz Sascha Peilicke Scott Chacon Sebastian Schuberth @@ -62,6 +67,3 @@ Tim Clem Tim Harder Trent Mick Vicent Marti -antong -kelly.leahy -schu diff --git a/CMakeLists.txt b/CMakeLists.txt index a57394640..17e4ff2de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,11 +39,14 @@ ENDIF() # Specify sha1 implementation IF (SHA1_TYPE STREQUAL "ppc") ADD_DEFINITIONS(-DPPC_SHA1) - FILE(GLOB SRC_SHA1 src/ppc/*.c src/ppc/*.S) -ELSEIF (OPENSSL_FOUND) # libcrypto's implementation is faster than ours - ADD_DEFINITIONS(-DOPENSSL_SHA) -ELSE () - FILE(GLOB SRC_SHA1 src/sha1/*.c) + FILE(GLOB SRC_SHA1 src/hash/hash_ppc.c src/hash/hash_ppc_core.S) +ELSEIF (WIN32 AND NOT MINGW AND NOT SHA1_TYPE STREQUAL "builtin") + ADD_DEFINITIONS(-DWIN32_SHA1) + FILE(GLOB SRC_SHA1 src/hash/hash_win32.c) +ELSEIF (OPENSSL_FOUND AND NOT SHA1_TYPE STREQUAL "builtin") + ADD_DEFINITIONS(-DOPENSSL_SHA1) +ELSE() + FILE(GLOB SRC_SHA1 src/hash/hash_generic.c) ENDIF() IF (NOT WIN32) @@ -85,7 +88,9 @@ IF (MSVC) # Default to stdcall, as that's what the CLR expects and how the Windows API is built OPTION (STDCALL "Buildl libgit2 with the __stdcall convention" ON) - SET(CMAKE_C_FLAGS "/W4 /MP /nologo /Zi ${CMAKE_C_FLAGS}") + STRING(REPLACE "/Zm1000" " " CMAKE_C_FLAGS "${CMAKE_C_FLAGS}") + + SET(CMAKE_C_FLAGS "/MP /nologo /Zi ${CMAKE_C_FLAGS}") IF (STDCALL) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /Gz") ENDIF () @@ -94,24 +99,32 @@ IF (MSVC) SET(WIN_RC "src/win32/git2.rc") # Precompiled headers + ELSE () - SET(CMAKE_C_FLAGS_DEBUG "-O0 -g ${CMAKE_C_FLAGS}") - SET(CMAKE_C_FLAGS "-O2 -g -D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") + SET(CMAKE_C_FLAGS "-D_GNU_SOURCE -Wall -Wextra -Wno-missing-field-initializers -Wstrict-aliasing=2 -Wstrict-prototypes -Wmissing-prototypes ${CMAKE_C_FLAGS}") IF (MINGW) # MinGW always does PIC and complains if we tell it to STRING(REGEX REPLACE "-fPIC" "" CMAKE_SHARED_LIBRARY_C_FLAGS "${CMAKE_SHARED_LIBRARY_C_FLAGS}") ELSE () SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -fPIC") ENDIF () + IF (APPLE) # Apple deprecated OpenSSL + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-deprecated-declarations") + ENDIF () IF (PROFILE) SET(CMAKE_C_FLAGS "-pg ${CMAKE_C_FLAGS}") SET(CMAKE_EXE_LINKER_FLAGS "-pg ${CMAKE_EXE_LINKER_FLAGS}") ENDIF () ENDIF() -# Build Debug by default -IF (NOT CMAKE_BUILD_TYPE) - SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) -ENDIF () +IF( NOT CMAKE_CONFIGURATION_TYPES ) + # Build Debug by default + IF (NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE) + ENDIF () +ELSE() + # Using a multi-configuration generator eg MSVC or Xcode + # that uses CMAKE_CONFIGURATION_TYPES and not CMAKE_BUILD_TYPE +ENDIF() IF (OPENSSL_FOUND) ADD_DEFINITIONS(-DGIT_SSL) @@ -159,7 +172,7 @@ SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) -IF (MSVC) +IF (MSVC_IDE) # Precompiled headers SET_TARGET_PROPERTIES(git2 PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") @@ -198,7 +211,7 @@ IF (BUILD_CLAR) ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) - IF (MSVC) + IF (MSVC_IDE) # Precompiled headers SET_TARGET_PROPERTIES(libgit2_clar PROPERTIES COMPILE_FLAGS "/Yuprecompiled.h /FIprecompiled.h") ENDIF () diff --git a/Makefile.embed b/Makefile.embed index b31a06e4b..76b4d3cda 100644 --- a/Makefile.embed +++ b/Makefile.embed @@ -15,7 +15,7 @@ INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES) CFLAGS= -g $(DEFINES) -Wall -Wextra -O2 $(EXTRA_CFLAGS) -SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) $(wildcard src/sha1/*.c) +SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c ifeq ($(PLATFORM),Msys) SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c @@ -58,10 +58,6 @@ To install the library you can specify the install prefix by setting: $ cmake .. -DCMAKE_INSTALL_PREFIX=/install/prefix $ cmake --build . --target install -If you want to build a universal binary for Mac OS X, CMake sets it -all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` -when configuring. - For more advanced use or questions about CMake please read <http://www.cmake.org/Wiki/CMake_FAQ>. The following CMake variables are declared: @@ -70,8 +66,39 @@ The following CMake variables are declared: - `LIB_INSTALL_DIR`: Where to install libraries to. - `INCLUDE_INSTALL_DIR`: Where to install headers to. - `BUILD_SHARED_LIBS`: Build libgit2 as a Shared Library (defaults to ON) -- `BUILD_CLAR`: Build [Clar](https://github.com/tanoku/clar)-based test suite (defaults to ON) +- `BUILD_CLAR`: Build [Clar](https://github.com/vmg/clar)-based test suite (defaults to ON) - `THREADSAFE`: Build libgit2 with threading support (defaults to OFF) +- `STDCALL`: Build libgit2 as `stdcall`. Turn off for `cdecl` (Windows; defaults to ON) + +Compiler and linker options +--------------------------- + +CMake lets you specify a few variables to control the behavior of the +compiler and linker. These flags are rarely used but can be useful for +64-bit to 32-bit cross-compilation. + +- `CMAKE_C_FLAGS`: Set your own compiler flags +- `CMAKE_FIND_ROOT_PATH`: Override the search path for libraries +- `ZLIB_LIBRARY`, `OPENSSL_SSL_LIBRARY` AND `OPENSSL_CRYPTO_LIBRARY`: +Tell CMake where to find those specific libraries + +MacOS X +------- + +If you want to build a universal binary for Mac OS X, CMake sets it +all up for you if you use `-DCMAKE_OSX_ARCHITECTURES="i386;x86_64"` +when configuring. + +Windows +------- + +You need to run the CMake commands from the Visual Studio command +prompt, not the regular or Windows SDK one. Select the right generator +for your version with the `-G "Visual Studio X" option. + +See [the wiki] +(https://github.com/libgit2/libgit2/wiki/Building-libgit2-on-Windows) +for more detailed instructions. Language Bindings ================================== diff --git a/deps/http-parser/http_parser.c b/deps/http-parser/http_parser.c index 438e81bec..b13a28c58 100644 --- a/deps/http-parser/http_parser.c +++ b/deps/http-parser/http_parser.c @@ -21,61 +21,100 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ -#include <http_parser.h> +#include "http_parser.h" #include <assert.h> #include <stddef.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <limits.h> +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif #ifndef MIN # define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ + (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ + (1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif -#if HTTP_PARSER_DEBUG -#define SET_ERRNO(e) \ -do { \ - parser->http_errno = (e); \ - parser->error_lineno = __LINE__; \ -} while (0) -#else #define SET_ERRNO(e) \ do { \ parser->http_errno = (e); \ } while(0) -#endif -#define CALLBACK2(FOR) \ +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ if (settings->on_##FOR) { \ if (0 != settings->on_##FOR(parser)) { \ SET_ERRNO(HPE_CB_##FOR); \ - return (p - data); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ } \ } \ } while (0) +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) -#define MARK(FOR) \ -do { \ - FOR##_mark = p; \ -} while (0) +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) -#define CALLBACK(FOR) \ +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ do { \ + assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ + \ if (FOR##_mark) { \ if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser, \ - FOR##_mark, \ - p - FOR##_mark)) \ - { \ + if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ SET_ERRNO(HPE_CB_##FOR); \ - return (p - data); \ + } \ + \ + /* We either errored above or got paused; get out */ \ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + return (ER); \ } \ } \ FOR##_mark = NULL; \ } \ } while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ + CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ + if (!FOR##_mark) { \ + FOR##_mark = p; \ + } \ +} while (0) #define PROXY_CONNECTION "proxy-connection" @@ -89,30 +128,10 @@ do { \ static const char *method_strings[] = - { "DELETE" - , "GET" - , "HEAD" - , "POST" - , "PUT" - , "CONNECT" - , "OPTIONS" - , "TRACE" - , "COPY" - , "LOCK" - , "MKCOL" - , "MOVE" - , "PROPFIND" - , "PROPPATCH" - , "UNLOCK" - , "REPORT" - , "MKACTIVITY" - , "CHECKOUT" - , "MERGE" - , "M-SEARCH" - , "NOTIFY" - , "SUBSCRIBE" - , "UNSUBSCRIBE" - , "PATCH" + { +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX }; @@ -133,9 +152,9 @@ static const char tokens[256] = { /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - ' ', '!', '"', '#', '$', '%', '&', '\'', + 0, '!', 0, '#', '$', '%', '&', '\'', /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 0, 0, '*', '+', 0, '-', '.', '/', + 0, 0, '*', '+', 0, '-', '.', 0, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ '0', '1', '2', '3', '4', '5', '6', '7', /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ @@ -155,7 +174,7 @@ static const char tokens[256] = { /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 'x', 'y', 'z', 0, '|', '}', '~', 0 }; + 'x', 'y', 'z', 0, '|', 0, '~', 0 }; static const int8_t unhex[256] = @@ -170,40 +189,48 @@ static const int8_t unhex[256] = }; -static const uint8_t normal_url_char[256] = { +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ - 0, 0, 0, 0, 0, 0, 0, 0, + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ - 0, 0, 0, 0, 0, 0, 0, 0, + 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ - 0, 0, 0, 0, 0, 0, 0, 0, + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ - 0, 0, 0, 0, 0, 0, 0, 0, + 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, 1, 1, 0, 1, 1, 1, 1, + 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ - 1, 1, 1, 1, 1, 1, 1, 0, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ - 1, 1, 1, 1, 1, 1, 1, 1, + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ - 1, 1, 1, 1, 1, 1, 1, 0, }; + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; +#undef T enum state { s_dead = 1 /* important that this is > 0 */ @@ -231,8 +258,9 @@ enum state , s_req_schema , s_req_schema_slash , s_req_schema_slash_slash - , s_req_host - , s_req_port + , s_req_server_start + , s_req_server + , s_req_server_with_at , s_req_path , s_req_query_string_start , s_req_query_string @@ -261,9 +289,11 @@ enum state , s_chunk_size , s_chunk_parameters , s_chunk_size_almost_done - + , s_headers_almost_done - /* Important: 's_headers_almost_done' must be the last 'header' state. All + , s_headers_done + + /* Important: 's_headers_done' must be the last 'header' state. All * states beyond this must be 'body' states. It is used for overflow * checking. See the PARSING_HEADER() macro. */ @@ -274,10 +304,12 @@ enum state , s_body_identity , s_body_identity_eof + + , s_message_done }; -#define PARSING_HEADER(state) (state <= s_headers_almost_done) +#define PARSING_HEADER(state) (state <= s_headers_done) enum header_states @@ -306,22 +338,43 @@ enum header_states , h_connection_close }; +enum http_host_state + { + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_port_start + , s_http_host_port +}; /* Macros for character classes; depends on strict-mode */ #define CR '\r' #define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) -#define TOKEN(c) (tokens[(unsigned char)c]) #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ + (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ + (c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ + (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ + (c) == '$' || (c) == ',') #if HTTP_PARSER_STRICT -#define IS_URL_CHAR(c) (normal_url_char[(unsigned char) (c)]) +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) #define IS_URL_CHAR(c) \ - (normal_url_char[(unsigned char) (c)] || ((c) & 0x80)) + (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') #endif @@ -355,6 +408,166 @@ static struct { }; #undef HTTP_STRERROR_GEN +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} size_t http_parser_execute (http_parser *parser, const http_parser_settings *settings, @@ -363,27 +576,24 @@ size_t http_parser_execute (http_parser *parser, { char c, ch; int8_t unhex_val; - const char *p = data, *pe; - size_t to_read; - enum state state; - enum header_states header_state; - size_t index = parser->index; - size_t nread = parser->nread; - const char *header_field_mark, *header_value_mark, *url_mark; - const char *matcher; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { return 0; } - state = (enum state) parser->state; - header_state = (enum header_states) parser->header_state; - if (len == 0) { - switch (state) { + switch (parser->state) { case s_body_identity_eof: - CALLBACK2(message_complete); + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); return 0; case s_dead: @@ -398,42 +608,49 @@ size_t http_parser_execute (http_parser *parser, } } - /* technically we could combine all of these (except for url_mark) into one - variable, saving stack space, but it seems more clear to have them - separated. */ - header_field_mark = 0; - header_value_mark = 0; - url_mark = 0; - if (state == s_header_field) + if (parser->state == s_header_field) header_field_mark = data; - if (state == s_header_value) + if (parser->state == s_header_value) header_value_mark = data; - if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash - || state == s_req_schema_slash_slash || state == s_req_port - || state == s_req_query_string_start || state == s_req_query_string - || state == s_req_host - || state == s_req_fragment_start || state == s_req_fragment) + switch (parser->state) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: url_mark = data; + break; + } - for (p=data, pe=data+len; p != pe; p++) { + for (p=data; p != data + len; p++) { ch = *p; - if (PARSING_HEADER(state)) { - ++nread; + if (PARSING_HEADER(parser->state)) { + ++parser->nread; /* Buffer overflow attack */ - if (nread > HTTP_MAX_HEADER_SIZE) { + if (parser->nread > HTTP_MAX_HEADER_SIZE) { SET_ERRNO(HPE_HEADER_OVERFLOW); goto error; } } - switch (state) { + reexecute_byte: + switch (parser->state) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ + if (ch == CR || ch == LF) + break; + SET_ERRNO(HPE_CLOSED_CONNECTION); goto error; @@ -442,23 +659,25 @@ size_t http_parser_execute (http_parser *parser, if (ch == CR || ch == LF) break; parser->flags = 0; - parser->content_length = -1; + parser->content_length = ULLONG_MAX; - CALLBACK2(message_begin); + if (ch == 'H') { + parser->state = s_res_or_resp_H; - if (ch == 'H') - state = s_res_or_resp_H; - else { + CALLBACK_NOTIFY(message_begin); + } else { parser->type = HTTP_REQUEST; - goto start_req_method_assign; + parser->state = s_start_req; + goto reexecute_byte; } + break; } case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; - state = s_res_HT; + parser->state = s_res_HT; } else { if (ch != 'E') { SET_ERRNO(HPE_INVALID_CONSTANT); @@ -467,21 +686,19 @@ size_t http_parser_execute (http_parser *parser, parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; - index = 2; - state = s_req_method; + parser->index = 2; + parser->state = s_req_method; } break; case s_start_res: { parser->flags = 0; - parser->content_length = -1; - - CALLBACK2(message_begin); + parser->content_length = ULLONG_MAX; switch (ch) { case 'H': - state = s_res_H; + parser->state = s_res_H; break; case CR: @@ -492,44 +709,46 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } + + CALLBACK_NOTIFY(message_begin); break; } case s_res_H: STRICT_CHECK(ch != 'T'); - state = s_res_HT; + parser->state = s_res_HT; break; case s_res_HT: STRICT_CHECK(ch != 'T'); - state = s_res_HTT; + parser->state = s_res_HTT; break; case s_res_HTT: STRICT_CHECK(ch != 'P'); - state = s_res_HTTP; + parser->state = s_res_HTTP; break; case s_res_HTTP: STRICT_CHECK(ch != '/'); - state = s_res_first_http_major; + parser->state = s_res_first_http_major; break; case s_res_first_http_major: - if (ch < '1' || ch > '9') { + if (ch < '0' || ch > '9') { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - state = s_res_http_major; + parser->state = s_res_http_major; break; /* major HTTP version or dot */ case s_res_http_major: { if (ch == '.') { - state = s_res_first_http_minor; + parser->state = s_res_first_http_minor; break; } @@ -557,14 +776,14 @@ size_t http_parser_execute (http_parser *parser, } parser->http_minor = ch - '0'; - state = s_res_http_minor; + parser->state = s_res_http_minor; break; /* minor HTTP version or end of request line */ case s_res_http_minor: { if (ch == ' ') { - state = s_res_first_status_code; + parser->state = s_res_first_status_code; break; } @@ -595,7 +814,7 @@ size_t http_parser_execute (http_parser *parser, goto error; } parser->status_code = ch - '0'; - state = s_res_status_code; + parser->state = s_res_status_code; break; } @@ -604,13 +823,13 @@ size_t http_parser_execute (http_parser *parser, if (!IS_NUM(ch)) { switch (ch) { case ' ': - state = s_res_status; + parser->state = s_res_status; break; case CR: - state = s_res_line_almost_done; + parser->state = s_res_line_almost_done; break; case LF: - state = s_header_field_start; + parser->state = s_header_field_start; break; default: SET_ERRNO(HPE_INVALID_STATUS); @@ -634,19 +853,19 @@ size_t http_parser_execute (http_parser *parser, /* the human readable status. e.g. "NOT FOUND" * we are not humans so just ignore this */ if (ch == CR) { - state = s_res_line_almost_done; + parser->state = s_res_line_almost_done; break; } if (ch == LF) { - state = s_header_field_start; + parser->state = s_header_field_start; break; } break; case s_res_line_almost_done: STRICT_CHECK(ch != LF); - state = s_header_field_start; + parser->state = s_header_field_start; break; case s_start_req: @@ -654,18 +873,15 @@ size_t http_parser_execute (http_parser *parser, if (ch == CR || ch == LF) break; parser->flags = 0; - parser->content_length = -1; - - CALLBACK2(message_begin); + parser->content_length = ULLONG_MAX; if (!IS_ALPHA(ch)) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } - start_req_method_assign: parser->method = (enum http_method) 0; - index = 1; + parser->index = 1; switch (ch) { case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; case 'D': parser->method = HTTP_DELETE; break; @@ -676,341 +892,158 @@ size_t http_parser_execute (http_parser *parser, case 'N': parser->method = HTTP_NOTIFY; break; case 'O': parser->method = HTTP_OPTIONS; break; case 'P': parser->method = HTTP_POST; - /* or PROPFIND or PROPPATCH or PUT or PATCH */ + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; case 'R': parser->method = HTTP_REPORT; break; - case 'S': parser->method = HTTP_SUBSCRIBE; break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } - state = s_req_method; + parser->state = s_req_method; + + CALLBACK_NOTIFY(message_begin); + break; } case s_req_method: { + const char *matcher; if (ch == '\0') { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; - if (ch == ' ' && matcher[index] == '\0') { - state = s_req_spaces_before_url; - } else if (ch == matcher[index]) { + if (ch == ' ' && matcher[parser->index] == '\0') { + parser->state = s_req_spaces_before_url; + } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (parser->method == HTTP_CONNECT) { - if (index == 1 && ch == 'H') { + if (parser->index == 1 && ch == 'H') { parser->method = HTTP_CHECKOUT; - } else if (index == 2 && ch == 'P') { + } else if (parser->index == 2 && ch == 'P') { parser->method = HTTP_COPY; } else { goto error; } } else if (parser->method == HTTP_MKCOL) { - if (index == 1 && ch == 'O') { + if (parser->index == 1 && ch == 'O') { parser->method = HTTP_MOVE; - } else if (index == 1 && ch == 'E') { + } else if (parser->index == 1 && ch == 'E') { parser->method = HTTP_MERGE; - } else if (index == 1 && ch == '-') { + } else if (parser->index == 1 && ch == '-') { parser->method = HTTP_MSEARCH; - } else if (index == 2 && ch == 'A') { + } else if (parser->index == 2 && ch == 'A') { parser->method = HTTP_MKACTIVITY; } else { goto error; } - } else if (index == 1 && parser->method == HTTP_POST) { + } else if (parser->method == HTTP_SUBSCRIBE) { + if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_SEARCH; + } else { + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { if (ch == 'R') { parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ } else if (ch == 'U') { - parser->method = HTTP_PUT; + parser->method = HTTP_PUT; /* or HTTP_PURGE */ } else if (ch == 'A') { parser->method = HTTP_PATCH; } else { goto error; } - } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') { - parser->method = HTTP_UNSUBSCRIBE; - } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') parser->method = HTTP_PURGE; + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') parser->method = HTTP_UNSUBSCRIBE; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { parser->method = HTTP_PROPPATCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; } - ++index; + ++parser->index; break; } + case s_req_spaces_before_url: { if (ch == ' ') break; - if (ch == '/' || ch == '*') { - MARK(url); - state = s_req_path; - break; + MARK(url); + if (parser->method == HTTP_CONNECT) { + parser->state = s_req_server_start; } - /* Proxied requests are followed by scheme of an absolute URI (alpha). - * CONNECT is followed by a hostname, which begins with alphanum. - * All other methods are followed by '/' or '*' (handled above). - */ - if (IS_ALPHA(ch) || (parser->method == HTTP_CONNECT && IS_NUM(ch))) { - MARK(url); - state = (parser->method == HTTP_CONNECT) ? s_req_host : s_req_schema; - break; + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; } - SET_ERRNO(HPE_INVALID_URL); - goto error; + break; } case s_req_schema: - { - if (IS_ALPHA(ch)) break; - - if (ch == ':') { - state = s_req_schema_slash; - break; - } - - SET_ERRNO(HPE_INVALID_URL); - goto error; - } - case s_req_schema_slash: - STRICT_CHECK(ch != '/'); - state = s_req_schema_slash_slash; - break; - case s_req_schema_slash_slash: - STRICT_CHECK(ch != '/'); - state = s_req_host; - break; - - case s_req_host: - { - if (IS_HOST_CHAR(ch)) break; - switch (ch) { - case ':': - state = s_req_port; - break; - case '/': - state = s_req_path; - break; - case ' ': - /* The request line looks like: - * "GET http://foo.bar.com HTTP/1.1" - * That is, there is no path. - */ - CALLBACK(url); - state = s_req_http_start; - break; - case '?': - state = s_req_query_string_start; - break; - default: - SET_ERRNO(HPE_INVALID_HOST); - goto error; - } - break; - } - - case s_req_port: - { - if (IS_NUM(ch)) break; - switch (ch) { - case '/': - state = s_req_path; - break; - case ' ': - /* The request line looks like: - * "GET http://foo.bar.com:1234 HTTP/1.1" - * That is, there is no path. - */ - CALLBACK(url); - state = s_req_http_start; - break; - case '?': - state = s_req_query_string_start; - break; - default: - SET_ERRNO(HPE_INVALID_PORT); - goto error; - } - break; - } - - case s_req_path: + case s_req_server_start: { - if (IS_URL_CHAR(ch)) break; - switch (ch) { + /* No whitespace allowed here */ case ' ': - CALLBACK(url); - state = s_req_http_start; - break; case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - state = s_req_query_string_start; - break; - case '#': - state = s_req_fragment_start; - break; - default: - SET_ERRNO(HPE_INVALID_PATH); + SET_ERRNO(HPE_INVALID_URL); goto error; - } - break; - } - - case s_req_query_string_start: - { - if (IS_URL_CHAR(ch)) { - state = s_req_query_string; - break; - } - - switch (ch) { - case '?': - break; /* XXX ignore extra '?' ... is this right? */ - case ' ': - CALLBACK(url); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '#': - state = s_req_fragment_start; - break; default: - SET_ERRNO(HPE_INVALID_QUERY_STRING); - goto error; + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } } - break; - } - case s_req_query_string: - { - if (IS_URL_CHAR(ch)) break; - - switch (ch) { - case '?': - /* allow extra '?' in query string */ - break; - case ' ': - CALLBACK(url); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '#': - state = s_req_fragment_start; - break; - default: - SET_ERRNO(HPE_INVALID_QUERY_STRING); - goto error; - } break; } + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: case s_req_fragment_start: - { - if (IS_URL_CHAR(ch)) { - state = s_req_fragment; - break; - } - - switch (ch) { - case ' ': - CALLBACK(url); - state = s_req_http_start; - break; - case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; - case LF: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - state = s_req_fragment; - break; - case '#': - break; - default: - SET_ERRNO(HPE_INVALID_FRAGMENT); - goto error; - } - break; - } - case s_req_fragment: { - if (IS_URL_CHAR(ch)) break; - switch (ch) { case ' ': - CALLBACK(url); - state = s_req_http_start; + parser->state = s_req_http_start; + CALLBACK_DATA(url); break; case CR: - CALLBACK(url); - parser->http_major = 0; - parser->http_minor = 9; - state = s_req_line_almost_done; - break; case LF: - CALLBACK(url); parser->http_major = 0; parser->http_minor = 9; - state = s_header_field_start; - break; - case '?': - case '#': + parser->state = (ch == CR) ? + s_req_line_almost_done : + s_header_field_start; + CALLBACK_DATA(url); break; default: - SET_ERRNO(HPE_INVALID_FRAGMENT); - goto error; + parser->state = parse_url_char((enum state)parser->state, ch); + if (parser->state == s_dead) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } } break; } @@ -1018,7 +1051,7 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_start: switch (ch) { case 'H': - state = s_req_http_H; + parser->state = s_req_http_H; break; case ' ': break; @@ -1030,22 +1063,22 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_H: STRICT_CHECK(ch != 'T'); - state = s_req_http_HT; + parser->state = s_req_http_HT; break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); - state = s_req_http_HTT; + parser->state = s_req_http_HTT; break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); - state = s_req_http_HTTP; + parser->state = s_req_http_HTTP; break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); - state = s_req_first_http_major; + parser->state = s_req_first_http_major; break; /* first digit of major HTTP version */ @@ -1056,14 +1089,14 @@ size_t http_parser_execute (http_parser *parser, } parser->http_major = ch - '0'; - state = s_req_http_major; + parser->state = s_req_http_major; break; /* major HTTP version or dot */ case s_req_http_major: { if (ch == '.') { - state = s_req_first_http_minor; + parser->state = s_req_first_http_minor; break; } @@ -1091,19 +1124,19 @@ size_t http_parser_execute (http_parser *parser, } parser->http_minor = ch - '0'; - state = s_req_http_minor; + parser->state = s_req_http_minor; break; /* minor HTTP version or end of request line */ case s_req_http_minor: { if (ch == CR) { - state = s_req_line_almost_done; + parser->state = s_req_line_almost_done; break; } if (ch == LF) { - state = s_header_field_start; + parser->state = s_header_field_start; break; } @@ -1133,23 +1166,22 @@ size_t http_parser_execute (http_parser *parser, goto error; } - state = s_header_field_start; + parser->state = s_header_field_start; break; } case s_header_field_start: - header_field_start: { if (ch == CR) { - state = s_headers_almost_done; + parser->state = s_headers_almost_done; break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ - state = s_headers_almost_done; - goto headers_almost_done; + parser->state = s_headers_almost_done; + goto reexecute_byte; } c = TOKEN(ch); @@ -1161,28 +1193,28 @@ size_t http_parser_execute (http_parser *parser, MARK(header_field); - index = 0; - state = s_header_field; + parser->index = 0; + parser->state = s_header_field; switch (c) { case 'c': - header_state = h_C; + parser->header_state = h_C; break; case 'p': - header_state = h_matching_proxy_connection; + parser->header_state = h_matching_proxy_connection; break; case 't': - header_state = h_matching_transfer_encoding; + parser->header_state = h_matching_transfer_encoding; break; case 'u': - header_state = h_matching_upgrade; + parser->header_state = h_matching_upgrade; break; default: - header_state = h_general; + parser->header_state = h_general; break; } break; @@ -1193,31 +1225,31 @@ size_t http_parser_execute (http_parser *parser, c = TOKEN(ch); if (c) { - switch (header_state) { + switch (parser->header_state) { case h_general: break; case h_C: - index++; - header_state = (c == 'o' ? h_CO : h_general); + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); break; case h_CO: - index++; - header_state = (c == 'n' ? h_CON : h_general); + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); break; case h_CON: - index++; + parser->index++; switch (c) { case 'n': - header_state = h_matching_connection; + parser->header_state = h_matching_connection; break; case 't': - header_state = h_matching_content_length; + parser->header_state = h_matching_content_length; break; default: - header_state = h_general; + parser->header_state = h_general; break; } break; @@ -1225,60 +1257,60 @@ size_t http_parser_execute (http_parser *parser, /* connection */ case h_matching_connection: - index++; - if (index > sizeof(CONNECTION)-1 - || c != CONNECTION[index]) { - header_state = h_general; - } else if (index == sizeof(CONNECTION)-2) { - header_state = h_connection; + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; } break; /* proxy-connection */ case h_matching_proxy_connection: - index++; - if (index > sizeof(PROXY_CONNECTION)-1 - || c != PROXY_CONNECTION[index]) { - header_state = h_general; - } else if (index == sizeof(PROXY_CONNECTION)-2) { - header_state = h_connection; + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; } break; /* content-length */ case h_matching_content_length: - index++; - if (index > sizeof(CONTENT_LENGTH)-1 - || c != CONTENT_LENGTH[index]) { - header_state = h_general; - } else if (index == sizeof(CONTENT_LENGTH)-2) { - header_state = h_content_length; + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; } break; /* transfer-encoding */ case h_matching_transfer_encoding: - index++; - if (index > sizeof(TRANSFER_ENCODING)-1 - || c != TRANSFER_ENCODING[index]) { - header_state = h_general; - } else if (index == sizeof(TRANSFER_ENCODING)-2) { - header_state = h_transfer_encoding; + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; } break; /* upgrade */ case h_matching_upgrade: - index++; - if (index > sizeof(UPGRADE)-1 - || c != UPGRADE[index]) { - header_state = h_general; - } else if (index == sizeof(UPGRADE)-2) { - header_state = h_upgrade; + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; } break; @@ -1286,7 +1318,7 @@ size_t http_parser_execute (http_parser *parser, case h_content_length: case h_transfer_encoding: case h_upgrade: - if (ch != ' ') header_state = h_general; + if (ch != ' ') parser->header_state = h_general; break; default: @@ -1297,20 +1329,20 @@ size_t http_parser_execute (http_parser *parser, } if (ch == ':') { - CALLBACK(header_field); - state = s_header_value_start; + parser->state = s_header_value_start; + CALLBACK_DATA(header_field); break; } if (ch == CR) { - state = s_header_almost_done; - CALLBACK(header_field); + parser->state = s_header_almost_done; + CALLBACK_DATA(header_field); break; } if (ch == LF) { - CALLBACK(header_field); - state = s_header_field_start; + parser->state = s_header_field_start; + CALLBACK_DATA(header_field); break; } @@ -1324,36 +1356,36 @@ size_t http_parser_execute (http_parser *parser, MARK(header_value); - state = s_header_value; - index = 0; + parser->state = s_header_value; + parser->index = 0; if (ch == CR) { - CALLBACK(header_value); - header_state = h_general; - state = s_header_almost_done; + parser->header_state = h_general; + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); break; } if (ch == LF) { - CALLBACK(header_value); - state = s_header_field_start; + parser->state = s_header_field_start; + CALLBACK_DATA(header_value); break; } c = LOWER(ch); - switch (header_state) { + switch (parser->header_state) { case h_upgrade: parser->flags |= F_UPGRADE; - header_state = h_general; + parser->header_state = h_general; break; case h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { - header_state = h_matching_transfer_encoding_chunked; + parser->header_state = h_matching_transfer_encoding_chunked; } else { - header_state = h_general; + parser->header_state = h_general; } break; @@ -1369,17 +1401,17 @@ size_t http_parser_execute (http_parser *parser, case h_connection: /* looking for 'Connection: keep-alive' */ if (c == 'k') { - header_state = h_matching_connection_keep_alive; + parser->header_state = h_matching_connection_keep_alive; /* looking for 'Connection: close' */ } else if (c == 'c') { - header_state = h_matching_connection_close; + parser->header_state = h_matching_connection_close; } else { - header_state = h_general; + parser->header_state = h_general; } break; default: - header_state = h_general; + parser->header_state = h_general; break; } break; @@ -1389,19 +1421,20 @@ size_t http_parser_execute (http_parser *parser, { if (ch == CR) { - CALLBACK(header_value); - state = s_header_almost_done; + parser->state = s_header_almost_done; + CALLBACK_DATA(header_value); break; } if (ch == LF) { - CALLBACK(header_value); - goto header_almost_done; + parser->state = s_header_almost_done; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; } c = LOWER(ch); - switch (header_state) { + switch (parser->header_state) { case h_general: break; @@ -1411,70 +1444,83 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: + { + uint64_t t; + if (ch == ' ') break; + if (!IS_NUM(ch)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } - parser->content_length *= 10; - parser->content_length += ch - '0'; + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; break; + } /* Transfer-Encoding: chunked */ case h_matching_transfer_encoding_chunked: - index++; - if (index > sizeof(CHUNKED)-1 - || c != CHUNKED[index]) { - header_state = h_general; - } else if (index == sizeof(CHUNKED)-2) { - header_state = h_transfer_encoding_chunked; + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + parser->header_state = h_transfer_encoding_chunked; } break; /* looking for 'Connection: keep-alive' */ case h_matching_connection_keep_alive: - index++; - if (index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[index]) { - header_state = h_general; - } else if (index == sizeof(KEEP_ALIVE)-2) { - header_state = h_connection_keep_alive; + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + parser->header_state = h_connection_keep_alive; } break; /* looking for 'Connection: close' */ case h_matching_connection_close: - index++; - if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) { - header_state = h_general; - } else if (index == sizeof(CLOSE)-2) { - header_state = h_connection_close; + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CLOSE)-2) { + parser->header_state = h_connection_close; } break; case h_transfer_encoding_chunked: case h_connection_keep_alive: case h_connection_close: - if (ch != ' ') header_state = h_general; + if (ch != ' ') parser->header_state = h_general; break; default: - state = s_header_value; - header_state = h_general; + parser->state = s_header_value; + parser->header_state = h_general; break; } break; } case s_header_almost_done: - header_almost_done: { STRICT_CHECK(ch != LF); - state = s_header_value_lws; + parser->state = s_header_value_lws; - switch (header_state) { + switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; break; @@ -1487,44 +1533,47 @@ size_t http_parser_execute (http_parser *parser, default: break; } + break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') - state = s_header_value_start; + parser->state = s_header_value_start; else { - state = s_header_field_start; - goto header_field_start; + parser->state = s_header_field_start; + goto reexecute_byte; } break; } case s_headers_almost_done: - headers_almost_done: { STRICT_CHECK(ch != LF); if (parser->flags & F_TRAILING) { /* End of a chunked request */ - CALLBACK2(message_complete); - state = NEW_MESSAGE(); + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); break; } - nread = 0; + parser->state = s_headers_done; - if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) { - parser->upgrade = 1; - } + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we * will interpret that as saying that this message has no body. This * is needed for the annoying case of recieving a response to a HEAD * request. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. */ if (settings->on_headers_complete) { switch (settings->on_headers_complete(parser)) { @@ -1536,40 +1585,54 @@ size_t http_parser_execute (http_parser *parser, break; default: - parser->state = state; SET_ERRNO(HPE_CB_headers_complete); return p - data; /* Error */ } } + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return p - data; + } + + goto reexecute_byte; + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + /* Exit, the rest of the connect is in a different protocol. */ if (parser->upgrade) { - CALLBACK2(message_complete); + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); return (p - data) + 1; } if (parser->flags & F_SKIPBODY) { - CALLBACK2(message_complete); - state = NEW_MESSAGE(); + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ - state = s_chunk_size_start; + parser->state = s_chunk_size_start; } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ - CALLBACK2(message_complete); - state = NEW_MESSAGE(); - } else if (parser->content_length > 0) { + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ - state = s_body_identity; + parser->state = s_body_identity; } else { - if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ - CALLBACK2(message_complete); - state = NEW_MESSAGE(); + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ - state = s_body_identity_eof; + parser->state = s_body_identity_eof; } } } @@ -1578,30 +1641,56 @@ size_t http_parser_execute (http_parser *parser, } case s_body_identity: - to_read = (size_t)MIN(pe - p, parser->content_length); - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - parser->content_length -= to_read; - if (parser->content_length == 0) { - CALLBACK2(message_complete); - state = NEW_MESSAGE(); - } + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + parser->state = s_message_done; + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + goto reexecute_byte; } + break; + } /* read until EOF */ case s_body_identity_eof: - to_read = pe - p; - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - } + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + parser->state = NEW_MESSAGE(); + CALLBACK_NOTIFY(message_complete); break; case s_chunk_size_start: { - assert(nread == 1); + assert(parser->nread == 1); assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; @@ -1611,16 +1700,18 @@ size_t http_parser_execute (http_parser *parser, } parser->content_length = unhex_val; - state = s_chunk_size; + parser->state = s_chunk_size; break; } case s_chunk_size: { + uint64_t t; + assert(parser->flags & F_CHUNKED); if (ch == CR) { - state = s_chunk_size_almost_done; + parser->state = s_chunk_size_almost_done; break; } @@ -1628,7 +1719,7 @@ size_t http_parser_execute (http_parser *parser, if (unhex_val == -1) { if (ch == ';' || ch == ' ') { - state = s_chunk_parameters; + parser->state = s_chunk_parameters; break; } @@ -1636,8 +1727,17 @@ size_t http_parser_execute (http_parser *parser, goto error; } - parser->content_length *= 16; - parser->content_length += unhex_val; + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? */ + if (t < parser->content_length || t == ULLONG_MAX) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; break; } @@ -1646,7 +1746,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { - state = s_chunk_size_almost_done; + parser->state = s_chunk_size_almost_done; break; } break; @@ -1657,46 +1757,53 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); - nread = 0; + parser->nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; - state = s_header_field_start; + parser->state = s_header_field_start; } else { - state = s_chunk_data; + parser->state = s_chunk_data; } break; } case s_chunk_data: { - assert(parser->flags & F_CHUNKED); + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); - to_read = (size_t)MIN(pe - p, parser->content_length); + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); - if (to_read > 0) { - if (settings->on_body) settings->on_body(parser, p, to_read); - p += to_read - 1; - } + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; - if ((signed)to_read == parser->content_length) { - state = s_chunk_data_almost_done; + if (parser->content_length == 0) { + parser->state = s_chunk_data_almost_done; } - parser->content_length -= to_read; break; } case s_chunk_data_almost_done: assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); STRICT_CHECK(ch != CR); - state = s_chunk_data_done; + parser->state = s_chunk_data_done; + CALLBACK_DATA(body); break; case s_chunk_data_done: assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); - state = s_chunk_size_start; + parser->nread = 0; + parser->state = s_chunk_size_start; break; default: @@ -1706,14 +1813,25 @@ size_t http_parser_execute (http_parser *parser, } } - CALLBACK(header_field); - CALLBACK(header_value); - CALLBACK(url); + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0)) <= 1); - parser->state = state; - parser->header_state = header_state; - parser->index = (unsigned char) index; - parser->nread = nread; + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); return len; @@ -1726,43 +1844,65 @@ error: } +/* Does the parser need to see an EOF to find the end of the message? */ int -http_should_keep_alive (http_parser *parser) +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) { if (parser->http_major > 0 && parser->http_minor > 0) { /* HTTP/1.1 */ if (parser->flags & F_CONNECTION_CLOSE) { return 0; - } else { - return 1; } } else { /* HTTP/1.0 or earlier */ - if (parser->flags & F_CONNECTION_KEEP_ALIVE) { - return 1; - } else { + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { return 0; } } + + return !http_message_needs_eof(parser); } -const char * http_method_str (enum http_method m) +const char * +http_method_str (enum http_method m) { - return method_strings[m]; + return ELEM_AT(method_strings, m, "<unknown>"); } void http_parser_init (http_parser *parser, enum http_parser_type t) { + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; parser->type = t; parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); - parser->nread = 0; - parser->upgrade = 0; - parser->flags = 0; - parser->method = 0; - parser->http_errno = 0; + parser->http_errno = HPE_OK; } const char * @@ -1776,3 +1916,259 @@ http_errno_description(enum http_errno err) { assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); return http_strerror_tab[err].description; } + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':') { + return s_http_host_v6; + } + + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + uf = old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} diff --git a/deps/http-parser/http_parser.h b/deps/http-parser/http_parser.h index b6f6e9978..4f20396c6 100644 --- a/deps/http-parser/http_parser.h +++ b/deps/http-parser/http_parser.h @@ -24,16 +24,25 @@ extern "C" { #endif -#define HTTP_PARSER_VERSION_MAJOR 1 +#define HTTP_PARSER_VERSION_MAJOR 2 #define HTTP_PARSER_VERSION_MINOR 0 -#ifdef _MSC_VER - /* disable silly warnings */ -# pragma warning(disable: 4127 4214) -#endif - #include <sys/types.h> -#include "git2/common.h" +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +#include <BaseTsd.h> +typedef __int8 int8_t; +typedef unsigned __int8 uint8_t; +typedef __int16 int16_t; +typedef unsigned __int16 uint16_t; +typedef __int32 int32_t; +typedef unsigned __int32 uint32_t; +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; +typedef SIZE_T size_t; +typedef SSIZE_T ssize_t; +#else +#include <stdint.h> +#endif /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run * faster @@ -42,21 +51,12 @@ extern "C" { # define HTTP_PARSER_STRICT 1 #endif -/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to - * the error reporting facility. - */ -#ifndef HTTP_PARSER_DEBUG -# define HTTP_PARSER_DEBUG 0 -#endif - - /* Maximium header size allowed */ #define HTTP_MAX_HEADER_SIZE (80*1024) typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings; -typedef struct http_parser_result http_parser_result; /* Callbacks should return non-zero to indicate an error. The parser will @@ -69,7 +69,7 @@ typedef struct http_parser_result http_parser_result; * chunked' headers that indicate the presence of a body. * * http_data_cb does not return data chunks. It will be call arbitrarally - * many times for each string. E.G. you might get 10 callbacks for "on_path" + * many times for each string. E.G. you might get 10 callbacks for "on_url" * each providing just a few characters more data. */ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); @@ -77,36 +77,44 @@ typedef int (*http_cb) (http_parser*); /* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ + XX(0, DELETE, DELETE) \ + XX(1, GET, GET) \ + XX(2, HEAD, HEAD) \ + XX(3, POST, POST) \ + XX(4, PUT, PUT) \ + /* pathological */ \ + XX(5, CONNECT, CONNECT) \ + XX(6, OPTIONS, OPTIONS) \ + XX(7, TRACE, TRACE) \ + /* webdav */ \ + XX(8, COPY, COPY) \ + XX(9, LOCK, LOCK) \ + XX(10, MKCOL, MKCOL) \ + XX(11, MOVE, MOVE) \ + XX(12, PROPFIND, PROPFIND) \ + XX(13, PROPPATCH, PROPPATCH) \ + XX(14, SEARCH, SEARCH) \ + XX(15, UNLOCK, UNLOCK) \ + /* subversion */ \ + XX(16, REPORT, REPORT) \ + XX(17, MKACTIVITY, MKACTIVITY) \ + XX(18, CHECKOUT, CHECKOUT) \ + XX(19, MERGE, MERGE) \ + /* upnp */ \ + XX(20, MSEARCH, M-SEARCH) \ + XX(21, NOTIFY, NOTIFY) \ + XX(22, SUBSCRIBE, SUBSCRIBE) \ + XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ + /* RFC-5789 */ \ + XX(24, PATCH, PATCH) \ + XX(25, PURGE, PURGE) \ + enum http_method - { HTTP_DELETE = 0 - , HTTP_GET - , HTTP_HEAD - , HTTP_POST - , HTTP_PUT - /* pathological */ - , HTTP_CONNECT - , HTTP_OPTIONS - , HTTP_TRACE - /* webdav */ - , HTTP_COPY - , HTTP_LOCK - , HTTP_MKCOL - , HTTP_MOVE - , HTTP_PROPFIND - , HTTP_PROPPATCH - , HTTP_UNLOCK - /* subversion */ - , HTTP_REPORT - , HTTP_MKACTIVITY - , HTTP_CHECKOUT - , HTTP_MERGE - /* upnp */ - , HTTP_MSEARCH - , HTTP_NOTIFY - , HTTP_SUBSCRIBE - , HTTP_UNSUBSCRIBE - /* RFC-5789 */ - , HTTP_PATCH + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX }; @@ -134,10 +142,7 @@ enum flags \ /* Callback-related errors */ \ XX(CB_message_begin, "the on_message_begin callback failed") \ - XX(CB_path, "the on_path callback failed") \ - XX(CB_query_string, "the on_query_string callback failed") \ XX(CB_url, "the on_url callback failed") \ - XX(CB_fragment, "the on_fragment callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \ @@ -168,6 +173,7 @@ enum flags XX(INVALID_CONSTANT, "invalid constant string") \ XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \ + XX(PAUSED, "parser is paused") \ XX(UNKNOWN, "an unknown error occurred") @@ -182,30 +188,23 @@ enum http_errno { /* Get an http_errno value from an http_parser */ #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) -/* Get the line number that generated the current error */ -#if HTTP_PARSER_DEBUG -#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) -#else -#define HTTP_PARSER_ERRNO_LINE(p) 0 -#endif - struct http_parser { /** PRIVATE **/ - unsigned char type : 2; - unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ - unsigned char state; - unsigned char header_state; - unsigned char index; + unsigned char type : 2; /* enum http_parser_type */ + unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned char state; /* enum state from http_parser.c */ + unsigned char header_state; /* enum header_state from http_parser.c */ + unsigned char index; /* index into current matcher */ - size_t nread; - int64_t content_length; + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ /** READ-ONLY **/ unsigned short http_major; unsigned short http_minor; unsigned short status_code; /* responses only */ - unsigned char method; /* requests only */ + unsigned char method; /* requests only */ unsigned char http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that. @@ -215,10 +214,6 @@ struct http_parser { */ unsigned char upgrade : 1; -#if HTTP_PARSER_DEBUG - uint32_t error_lineno; -#endif - /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ }; @@ -235,6 +230,36 @@ struct http_parser_settings { }; +enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + +/* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ +struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; +}; + + void http_parser_init(http_parser *parser, enum http_parser_type type); @@ -245,12 +270,12 @@ size_t http_parser_execute(http_parser *parser, /* If http_should_keep_alive() in the on_headers_complete or - * on_message_complete callback returns true, then this will be should be + * on_message_complete callback returns 0, then this should be * the last message on the connection. * If you are the server, respond with the "Connection: close" header. * If you are the client, close the connection. */ -int http_should_keep_alive(http_parser *parser); +int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m); @@ -261,6 +286,17 @@ const char *http_errno_name(enum http_errno err); /* Return a string description of the given error */ const char *http_errno_description(enum http_errno err); +/* Parse a URL; return nonzero on failure */ +int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + +/* Pause or un-pause the parser; a nonzero value pauses */ +void http_parser_pause(http_parser *parser, int paused); + +/* Checks if this is the final chunk of the body. */ +int http_body_is_final(const http_parser *parser); + #ifdef __cplusplus } #endif diff --git a/examples/Makefile b/examples/Makefile index fe99c75cb..da4df5240 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,7 +1,7 @@ .PHONY: all CC = gcc -CFLAGS = -g -I../include -I../src +CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes LFLAGS = -L../build -lgit2 -lz APPS = general showindex diff diff --git a/examples/diff.c b/examples/diff.c index b72a75e1c..38231d219 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -3,7 +3,7 @@ #include <stdlib.h> #include <string.h> -void check(int error, const char *message) +static void check(int error, const char *message) { if (error) { fprintf(stderr, "%s (%d)\n", message, error); @@ -11,7 +11,8 @@ void check(int error, const char *message) } } -int resolve_to_tree(git_repository *repo, const char *identifier, git_tree **tree) +static int resolve_to_tree( + git_repository *repo, const char *identifier, git_tree **tree) { int err = 0; size_t len = strlen(identifier); @@ -61,16 +62,18 @@ char *colors[] = { "\033[36m" /* cyan */ }; -int printer( +static int printer( void *data, - git_diff_delta *delta, - git_diff_range *range, + const git_diff_delta *delta, + const git_diff_range *range, char usage, const char *line, size_t line_len) { int *last_color = data, color = 0; + (void)delta; (void)range; (void)line_len; + if (*last_color >= 0) { switch (usage) { case GIT_DIFF_LINE_ADDITION: color = 3; break; @@ -93,7 +96,7 @@ int printer( return 0; } -int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) +static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) { size_t len = strlen(pattern); uint16_t strval; @@ -107,7 +110,7 @@ int check_uint16_param(const char *arg, const char *pattern, uint16_t *val) return 1; } -int check_str_param(const char *arg, const char *pattern, char **val) +static int check_str_param(const char *arg, const char *pattern, char **val) { size_t len = strlen(pattern); if (strncmp(arg, pattern, len)) @@ -116,7 +119,7 @@ int check_str_param(const char *arg, const char *pattern, char **val) return 1; } -void usage(const char *message, const char *arg) +static void usage(const char *message, const char *arg) { if (message && arg) fprintf(stderr, "%s: %s\n", message, arg); @@ -128,14 +131,15 @@ void usage(const char *message, const char *arg) int main(int argc, char *argv[]) { - char path[GIT_PATH_MAX]; git_repository *repo = NULL; git_tree *t1 = NULL, *t2 = NULL; - git_diff_options opts = {0}; + git_diff_options opts; git_diff_list *diff; int i, color = -1, compact = 0, cached = 0; char *a, *dir = ".", *treeish1 = NULL, *treeish2 = NULL; + memset(&opts, 0, sizeof(opts)); + /* parse arguments as copied from git-diff */ for (i = 1; i < argc; ++i) { @@ -200,22 +204,22 @@ int main(int argc, char *argv[]) /* nothing */ if (t1 && t2) - check(git_diff_tree_to_tree(repo, &opts, t1, t2, &diff), "Diff"); + check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff"); else if (t1 && cached) - check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff"); else if (t1) { git_diff_list *diff2; - check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); - check(git_diff_workdir_to_index(repo, &opts, &diff2), "Diff"); + check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff"); + check(git_diff_workdir_to_index(&diff2, repo, NULL, &opts), "Diff"); check(git_diff_merge(diff, diff2), "Merge diffs"); git_diff_list_free(diff2); } else if (cached) { check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD"); - check(git_diff_index_to_tree(repo, &opts, t1, &diff), "Diff"); + check(git_diff_index_to_tree(&diff, repo, t1, NULL, &opts), "Diff"); } else - check(git_diff_workdir_to_index(repo, &opts, &diff), "Diff"); + check(git_diff_workdir_to_index(&diff, repo, NULL, &opts), "Diff"); if (color >= 0) fputs(colors[0], stdout); diff --git a/examples/general.c b/examples/general.c index e001a6889..9ccb4c56e 100644 --- a/examples/general.c +++ b/examples/general.c @@ -239,7 +239,7 @@ int main (int argc, char** argv) // the tagger (a git_signature - name, email, timestamp), and the tag message. git_tag_target((git_object **)&commit, tag); tname = git_tag_name(tag); // "test" - ttype = git_tag_type(tag); // GIT_OBJ_COMMIT (otype enum) + ttype = git_tag_target_type(tag); // GIT_OBJ_COMMIT (otype enum) tmessage = git_tag_message(tag); // "tag message\n" printf("Tag Message: %s\n", tmessage); @@ -371,7 +371,7 @@ int main (int argc, char** argv) // All these properties are exported publicly in the `git_index_entry` struct ecount = git_index_entrycount(index); for (i = 0; i < ecount; ++i) { - git_index_entry *e = git_index_get(index, i); + git_index_entry *e = git_index_get_byindex(index, i); printf("path: %s\n", e->path); printf("mtime: %d\n", (int)e->mtime.seconds); diff --git a/examples/network/Makefile b/examples/network/Makefile index 835be24cc..ef3cec659 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -2,7 +2,8 @@ default: all CC = gcc CFLAGS += -g -CFLAGS += -I../../include -L../../build -L../.. -lgit2 -lpthread +CFLAGS += -I../../include +LDFLAGS += -L../../build -L../.. -lgit2 -lpthread OBJECTS = \ git2.o \ @@ -12,4 +13,4 @@ OBJECTS = \ index-pack.o all: $(OBJECTS) - $(CC) $(CFLAGS) -o git2 $(OBJECTS) + $(CC) $(CFLAGS) $(LDFLAGS) -o git2 $(OBJECTS) diff --git a/examples/network/clone.c b/examples/network/clone.c index fb571bd3a..30a4944c2 100644 --- a/examples/network/clone.c +++ b/examples/network/clone.c @@ -7,62 +7,78 @@ #include <pthread.h> #include <unistd.h> -struct dl_data { - git_indexer_stats fetch_stats; - git_indexer_stats checkout_stats; - git_checkout_opts opts; - int ret; - int finished; - const char *url; +typedef struct progress_data { + git_transfer_progress fetch_progress; + size_t completed_steps; + size_t total_steps; const char *path; -}; +} progress_data; -static void *clone_thread(void *ptr) +static void print_progress(const progress_data *pd) { - struct dl_data *data = (struct dl_data *)ptr; - git_repository *repo = NULL; + int network_percent = (100*pd->fetch_progress.received_objects) / pd->fetch_progress.total_objects; + int index_percent = (100*pd->fetch_progress.indexed_objects) / pd->fetch_progress.total_objects; + int checkout_percent = pd->total_steps > 0 + ? (100 * pd->completed_steps) / pd->total_steps + : 0.f; + int kbytes = pd->fetch_progress.received_bytes / 1024; - // Kick off the clone - data->ret = git_clone(&repo, data->url, data->path, - &data->fetch_stats, &data->checkout_stats, - &data->opts); - if (repo) git_repository_free(repo); - data->finished = 1; + printf("net %3d%% (%4d kb, %5d/%5d) / idx %3d%% (%5d/%5d) / chk %3d%% (%4" PRIuZ "/%4" PRIuZ ") %s\n", + network_percent, kbytes, + pd->fetch_progress.received_objects, pd->fetch_progress.total_objects, + index_percent, pd->fetch_progress.indexed_objects, pd->fetch_progress.total_objects, + checkout_percent, + pd->completed_steps, pd->total_steps, + pd->path); +} - pthread_exit(&data->ret); +static void fetch_progress(const git_transfer_progress *stats, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->fetch_progress = *stats; + print_progress(pd); +} +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + progress_data *pd = (progress_data*)payload; + pd->completed_steps = cur; + pd->total_steps = tot; + pd->path = path; + print_progress(pd); } int do_clone(git_repository *repo, int argc, char **argv) { - struct dl_data data = {0}; - pthread_t worker; + progress_data pd; + git_repository *cloned_repo = NULL; + git_checkout_opts checkout_opts; + const char *url = argv[1]; + const char *path = argv[2]; + int error; + + (void)repo; // unused // Validate args if (argc < 3) { - printf("USAGE: %s <url> <path>\n", argv[0]); + printf ("USAGE: %s <url> <path>\n", argv[0]); return -1; } - // Data for background thread - data.url = argv[1]; - data.path = argv[2]; - data.opts.disable_filters = 1; - printf("Cloning '%s' to '%s'\n", data.url, data.path); + // Set up options + memset(&checkout_opts, 0, sizeof(checkout_opts)); + checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + checkout_opts.progress_cb = checkout_progress; + memset(&pd, 0, sizeof(pd)); + checkout_opts.progress_payload = &pd; - // Create the worker thread - pthread_create(&worker, NULL, clone_thread, &data); - - // Watch for progress information - do { - usleep(10000); - printf("Fetch %d/%d – Checkout %d/%d\n", - data.fetch_stats.processed, data.fetch_stats.total, - data.checkout_stats.processed, data.checkout_stats.total); - } while (!data.finished); - printf("Fetch %d/%d – Checkout %d/%d\n", - data.fetch_stats.processed, data.fetch_stats.total, - data.checkout_stats.processed, data.checkout_stats.total); - - return data.ret; + // Do the clone + error = git_clone(&cloned_repo, url, path, &fetch_progress, &pd, &checkout_opts); + printf("\n"); + if (error != 0) { + const git_error *err = giterr_last(); + if (err) printf("ERROR %d: %s\n", err->klass, err->message); + else printf("ERROR %d: no detailed info\n", error); + } + else if (cloned_repo) git_repository_free(cloned_repo); + return error; } - diff --git a/examples/network/common.h b/examples/network/common.h index c82eaa1c8..a4cfa1a7e 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -12,4 +12,13 @@ int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); int do_clone(git_repository *repo, int argc, char **argv); +#ifndef PRIuZ +/* Define the printf format specifer to use for size_t output */ +#if defined(_MSC_VER) || defined(__MINGW32__) +# define PRIuZ "Iu" +#else +# define PRIuZ "zu" +#endif +#endif + #endif /* __COMMON_H__ */ diff --git a/examples/network/fetch.c b/examples/network/fetch.c index fa941b97a..9d1404ab4 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -8,8 +8,6 @@ struct dl_data { git_remote *remote; - git_off_t *bytes; - git_indexer_stats *stats; int ret; int finished; }; @@ -35,7 +33,7 @@ static void *download(void *ptr) // Download the packfile and index it. This function updates the // amount of received data and the indexer stats which lets you // inform the user about progress. - if (git_remote_download(data->remote, data->bytes, data->stats) < 0) { + if (git_remote_download(data->remote, NULL, NULL) < 0) { data->ret = -1; goto exit; } @@ -69,15 +67,14 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo int fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; - git_off_t bytes = 0; - git_indexer_stats stats; + const git_transfer_progress *stats; pthread_t worker; struct dl_data data; git_remote_callbacks callbacks; argc = argc; // Figure out whether it's a named remote or a URL - printf("Fetching %s\n", argv[1]); + printf("Fetching %s for repo %p\n", argv[1], repo); if (git_remote_load(&remote, repo, argv[1]) < 0) { if (git_remote_new(&remote, repo, NULL, argv[1], NULL) < 0) return -1; @@ -91,11 +88,10 @@ int fetch(git_repository *repo, int argc, char **argv) // Set up the information for the background worker thread data.remote = remote; - data.bytes = &bytes; - data.stats = &stats; data.ret = 0; data.finished = 0; - memset(&stats, 0, sizeof(stats)); + + stats = git_remote_stats(remote); pthread_create(&worker, NULL, download, &data); @@ -106,16 +102,18 @@ int fetch(git_repository *repo, int argc, char **argv) do { usleep(10000); - if (stats.total > 0) - printf("Received %d/%d objects (%d) in %d bytes\r", - stats.received, stats.total, stats.processed, bytes); + if (stats->total_objects > 0) + printf("Received %d/%d objects (%d) in %" PRIuZ " bytes\r", + stats->received_objects, stats->total_objects, + stats->indexed_objects, stats->received_bytes); } while (!data.finished); if (data.ret < 0) goto on_error; pthread_join(worker, NULL); - printf("\rReceived %d/%d objects in %zu bytes\n", stats.processed, stats.total, bytes); + printf("\rReceived %d/%d objects in %zu bytes\n", + stats->indexed_objects, stats->total_objects, stats->received_bytes); // Disconnect the underlying connection to prevent from idling. git_remote_disconnect(remote); diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c index 85aac4aff..4d3dc84d6 100644 --- a/examples/network/index-pack.c +++ b/examples/network/index-pack.c @@ -10,10 +10,10 @@ // This could be run in the main loop whilst the application waits for // the indexing to finish in a worker thread -static int index_cb(const git_indexer_stats *stats, void *data) +static int index_cb(const git_transfer_progress *stats, void *data) { data = data; - printf("\rProcessing %d of %d", stats->processed, stats->total); + printf("\rProcessing %d of %d", stats->indexed_objects, stats->total_objects); return 0; } @@ -21,7 +21,7 @@ static int index_cb(const git_indexer_stats *stats, void *data) int index_pack(git_repository *repo, int argc, char **argv) { git_indexer_stream *idx; - git_indexer_stats stats = {0, 0}; + git_transfer_progress stats = {0, 0}; int error, fd; char hash[GIT_OID_HEXSZ + 1] = {0}; ssize_t read_bytes; @@ -33,7 +33,7 @@ int index_pack(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - if (git_indexer_stream_new(&idx, ".") < 0) { + if (git_indexer_stream_new(&idx, ".", NULL, NULL) < 0) { puts("bad idx"); return -1; } @@ -63,7 +63,7 @@ int index_pack(git_repository *repo, int argc, char **argv) if ((error = git_indexer_stream_finalize(idx, &stats)) < 0) goto cleanup; - printf("\rIndexing %d of %d\n", stats.processed, stats.total); + printf("\rIndexing %d of %d\n", stats.indexed_objects, stats.total_objects); git_oid_fmt(hash, git_indexer_stream_hash(idx)); puts(hash); diff --git a/examples/showindex.c b/examples/showindex.c index 7f2130b90..4b50ffd0f 100644 --- a/examples/showindex.c +++ b/examples/showindex.c @@ -3,41 +3,53 @@ int main (int argc, char** argv) { - git_repository *repo; - git_index *index; - unsigned int i, e, ecount; - git_index_entry **entries; - git_oid oid; - - char out[41]; - out[40] = '\0'; - - git_repository_open(&repo, "/opt/libgit2-test/.git"); - - git_repository_index(&index, repo); - git_index_read(index); - - ecount = git_index_entrycount(index); - for (i = 0; i < ecount; ++i) { - git_index_entry *e = git_index_get(index, i); - - oid = e->oid; - git_oid_fmt(out, &oid); - - printf("File Path: %s\n", e->path); - printf(" Blob SHA: %s\n", out); - printf("File Size: %d\n", (int)e->file_size); - printf(" Device: %d\n", (int)e->dev); - printf(" Inode: %d\n", (int)e->ino); - printf(" UID: %d\n", (int)e->uid); - printf(" GID: %d\n", (int)e->gid); - printf(" ctime: %d\n", (int)e->ctime.seconds); - printf(" mtime: %d\n", (int)e->mtime.seconds); - printf("\n"); - } - - git_index_free(index); - - git_repository_free(repo); + git_repository *repo; + git_index *index; + unsigned int i, ecount; + char *dir = "."; + char out[41]; + out[40] = '\0'; + + if (argc > 1) + dir = argv[1]; + if (argc > 2) { + fprintf(stderr, "usage: showindex [<repo-dir>]\n"); + return 1; + } + + if (git_repository_open_ext(&repo, dir, 0, NULL) < 0) { + fprintf(stderr, "could not open repository: %s\n", dir); + return 1; + } + + git_repository_index(&index, repo); + git_index_read(index); + + ecount = git_index_entrycount(index); + if (!ecount) + printf("Empty index\n"); + + for (i = 0; i < ecount; ++i) { + const git_index_entry *e = git_index_get_byindex(index, i); + + git_oid_fmt(out, &e->oid); + + printf("File Path: %s\n", e->path); + printf(" Stage: %d\n", git_index_entry_stage(e)); + printf(" Blob SHA: %s\n", out); + printf("File Size: %d\n", (int)e->file_size); + printf(" Device: %d\n", (int)e->dev); + printf(" Inode: %d\n", (int)e->ino); + printf(" UID: %d\n", (int)e->uid); + printf(" GID: %d\n", (int)e->gid); + printf(" ctime: %d\n", (int)e->ctime.seconds); + printf(" mtime: %d\n", (int)e->mtime.seconds); + printf("\n"); + } + + git_index_free(index); + git_repository_free(repo); + + return 0; } diff --git a/include/git2.h b/include/git2.h index d55543986..501128c43 100644 --- a/include/git2.h +++ b/include/git2.h @@ -36,6 +36,7 @@ #include "git2/index.h" #include "git2/config.h" +#include "git2/transport.h" #include "git2/remote.h" #include "git2/clone.h" #include "git2/checkout.h" @@ -52,5 +53,6 @@ #include "git2/reset.h" #include "git2/message.h" #include "git2/pack.h" +#include "git2/stash.h" #endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h index b4f9ad081..27ecc7102 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -25,20 +25,121 @@ GIT_BEGIN_DECL * Checkout behavior flags * * These flags control what checkout does with files. Pass in a - * combination of these values OR'ed together. + * combination of these values OR'ed together. If you just pass zero + * (i.e. no flags), then you are effectively doing a "dry run" where no + * files will be modified. + * + * Checkout groups the working directory content into 3 classes of files: + * (1) files that don't need a change, and files that do need a change + * that either (2) we are allowed to modifed or (3) we are not. The flags + * you pass in will decide which files we are allowed to modify. + * + * By default, checkout is not allowed to modify any files. Anything + * needing a change would be considered a conflict. + * + * GIT_CHECKOUT_UPDATE_UNMODIFIED means that checkout is allowed to update + * any file where the working directory content matches the HEAD + * (e.g. either the files match or the file is absent in both places). + * + * GIT_CHECKOUT_UPDATE_MISSING means checkout can create a missing file + * that exists in the index and does not exist in the working directory. + * This is usually desirable for initial checkout, etc. Technically, the + * missing file differs from the HEAD, which is why this is separate. + * + * GIT_CHECKOUT_UPDATE_MODIFIED means checkout is allowed to update files + * where the working directory does not match the HEAD so long as the file + * actually exists in the HEAD. This option implies UPDATE_UNMODIFIED. + * + * GIT_CHECKOUT_UPDATE_UNTRACKED means checkout is allowed to update files + * even if there is a working directory version that does not exist in the + * HEAD (i.e. the file was independently created in the workdir). This + * implies UPDATE_UNMODIFIED | UPDATE_MISSING (but *not* UPDATE_MODIFIED). + * + * + * On top of these three basic strategies, there are some modifiers + * options that can be applied: + * + * If any files need update but are disallowed by the strategy, normally + * checkout calls the conflict callback (if given) and then aborts. + * GIT_CHECKOUT_ALLOW_CONFLICTS means it is okay to update the files that + * are allowed by the strategy even if there are conflicts. The conflict + * callbacks are still made, but non-conflicting files will be updated. + * + * Any unmerged entries in the index are automatically considered conflicts. + * If you want to proceed anyhow and just skip unmerged entries, you can use + * GIT_CHECKOUT_SKIP_UNMERGED which is less dangerous than just allowing all + * conflicts. Alternatively, use GIT_CHECKOUT_USE_OURS to proceed and + * checkout the stage 2 ("ours") version. GIT_CHECKOUT_USE_THEIRS means to + * proceed and use the stage 3 ("theirs") version. + * + * GIT_CHECKOUT_UPDATE_ONLY means that update is not allowed to create new + * files or delete old ones, only update existing content. With this + * flag, files that needs to be created or deleted are not conflicts - + * they are just skipped. This also skips typechanges to existing files + * (because the old would have to be removed). + * + * GIT_CHECKOUT_REMOVE_UNTRACKED means that files in the working directory + * that are untracked (and not ignored) will be removed altogether. These + * untracked files (that do not shadow index entries) are not considered + * conflicts and would normally be ignored. + * + * + * Checkout is "semi-atomic" as in it will go through the work to be done + * before making any changes and if may decide to abort if there are + * conflicts, or you can use the conflict callback to explicitly abort the + * action before any updates are made. Despite this, if a second process + * is modifying the filesystem while checkout is running, it can't + * guarantee that the choices is makes while initially examining the + * filesystem are still going to be correct as it applies them. */ typedef enum { - /** Checkout does not update any files in the working directory. */ - GIT_CHECKOUT_DEFAULT = (1 << 0), + GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */ - /** When a file exists and is modified, replace it with new version. */ - GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1), + /** Allow update of entries where working dir matches HEAD. */ + GIT_CHECKOUT_UPDATE_UNMODIFIED = (1u << 0), - /** When a file does not exist in the working directory, create it. */ - GIT_CHECKOUT_CREATE_MISSING = (1 << 2), + /** Allow update of entries where working dir does not have file. */ + GIT_CHECKOUT_UPDATE_MISSING = (1u << 1), + + /** Allow safe updates that cannot overwrite uncommited data */ + GIT_CHECKOUT_SAFE = + (GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING), + + /** Allow update of entries in working dir that are modified from HEAD. */ + GIT_CHECKOUT_UPDATE_MODIFIED = (1u << 2), + + /** Update existing untracked files that are now present in the index. */ + GIT_CHECKOUT_UPDATE_UNTRACKED = (1u << 3), + + /** Allow all updates to force working directory to look like index */ + GIT_CHECKOUT_FORCE = + (GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED), + + /** Allow checkout to make updates even if conflicts are found */ + GIT_CHECKOUT_ALLOW_CONFLICTS = (1u << 4), + + /** Remove untracked files not in index (that are not ignored) */ + GIT_CHECKOUT_REMOVE_UNTRACKED = (1u << 5), + + /** Only update existing files, don't create new ones */ + GIT_CHECKOUT_UPDATE_ONLY = (1u << 6), + + /** + * THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED + */ + + /** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */ + GIT_CHECKOUT_SKIP_UNMERGED = (1u << 10), + /** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */ + GIT_CHECKOUT_USE_OURS = (1u << 11), + /** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */ + GIT_CHECKOUT_USE_THEIRS = (1u << 12), + + /** Recursively checkout submodules with same options (NOT IMPLEMENTED) */ + GIT_CHECKOUT_UPDATE_SUBMODULES = (1u << 16), + /** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */ + GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1u << 17), - /** If an untracked file in found in the working dir, delete it. */ - GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3), } git_checkout_strategy_t; /** @@ -47,31 +148,38 @@ typedef enum { * Use zeros to indicate default settings. */ typedef struct git_checkout_opts { - unsigned int checkout_strategy; /** default: GIT_CHECKOUT_DEFAULT */ + unsigned int checkout_strategy; /** default will be a dry run */ + int disable_filters; /** don't apply filters like CRLF conversion */ int dir_mode; /** default is 0755 */ int file_mode; /** default is 0644 or 0755 as dictated by blob */ int file_open_flags; /** default is O_CREAT | O_TRUNC | O_WRONLY */ - /** Optional callback to notify the consumer of files that - * haven't be checked out because a modified version of them - * exist in the working directory. - * - * When provided, this callback will be invoked when the flag - * GIT_CHECKOUT_OVERWRITE_MODIFIED isn't part of the checkout strategy. + /** Optional callback made on files where the index differs from the + * working directory but the rules do not allow update. Return a + * non-zero value to abort the checkout. All such callbacks will be + * made before any changes are made to the working directory. */ - int (* skipped_notify_cb)( - const char *skipped_file, - const git_oid *blob_oid, - int file_mode, + int (*conflict_cb)( + const char *conflicting_path, + const git_oid *index_oid, + unsigned int index_mode, + unsigned int wd_mode, void *payload); + void *conflict_payload; - void *notify_payload; + /* Optional callback to notify the consumer of checkout progress. */ + void (*progress_cb)( + const char *path, + size_t completed_steps, + size_t total_steps, + void *payload); + void *progress_payload; - /** When not NULL, array of fnmatch patterns specifying - * which paths should be taken into account + /** When not zeroed out, array of fnmatch patterns specifying which + * paths should be taken into account, otherwise all files. */ - git_strarray paths; + git_strarray paths; } git_checkout_opts; /** @@ -80,29 +188,27 @@ typedef struct git_checkout_opts { * * @param repo repository to check out (must be non-bare) * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing * branch, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_head( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_checkout_opts *opts); /** * Updates files in the working tree to match the content of the index. * - * @param repo repository to check out (must be non-bare) + * @param repo repository into which to check out (must be non-bare) + * @param index index to be checked out (or NULL to use repository index) * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_index( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_index *index, + git_checkout_opts *opts); /** * Updates files in the index and working tree to match the content of the @@ -112,15 +218,13 @@ GIT_EXTERN(int) git_checkout_index( * @param treeish a commit, tag or tree which content will be used to update * the working directory * @param opts specifies checkout options (may be NULL) - * @param stats structure through which progress information is reported * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ GIT_EXTERN(int) git_checkout_tree( git_repository *repo, git_object *treeish, - git_checkout_opts *opts, - git_indexer_stats *stats); + git_checkout_opts *opts); /** @} */ GIT_END_DECL diff --git a/include/git2/clone.h b/include/git2/clone.h index c4dfc652b..7d8d32118 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -29,19 +29,22 @@ GIT_BEGIN_DECL * @param out pointer that will receive the resulting repository object * @param origin_url repository to clone from * @param workdir_path local directory to clone to - * @param fetch_stats pointer to structure that receives fetch progress - * information (may be NULL) + * @param fetch_progress_cb optional callback for fetch progress. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param fetch_progress_payload payload for fetch_progress_cb * @param checkout_opts options for the checkout step. If NULL, no checkout * is performed * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information * about the error) */ -GIT_EXTERN(int) git_clone(git_repository **out, - const char *origin_url, - const char *workdir_path, - git_indexer_stats *fetch_stats, - git_indexer_stats *checkout_stats, - git_checkout_opts *checkout_opts); +GIT_EXTERN(int) git_clone( + git_repository **out, + const char *origin_url, + const char *workdir_path, + git_transfer_progress_callback fetch_progress_cb, + void *fetch_progress_payload, + git_checkout_opts *checkout_opts); /** * Create a bare clone of a remote repository. @@ -49,13 +52,18 @@ GIT_EXTERN(int) git_clone(git_repository **out, * @param out pointer that will receive the resulting repository object * @param origin_url repository to clone from * @param dest_path local directory to clone to - * @param fetch_stats pointer to structure that receives fetch progress information (may be NULL) + * @param fetch_progress_cb optional callback for fetch progress. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param fetch_progress_payload payload for fetch_progress_cb * @return 0 on success, GIT_ERROR otherwise (use giterr_last for information about the error) */ -GIT_EXTERN(int) git_clone_bare(git_repository **out, - const char *origin_url, - const char *dest_path, - git_indexer_stats *fetch_stats); +GIT_EXTERN(int) git_clone_bare( + git_repository **out, + const char *origin_url, + const char *dest_path, + git_transfer_progress_callback fetch_progress_cb, + void *fetch_progress_payload); /** @} */ GIT_END_DECL diff --git a/include/git2/config.h b/include/git2/config.h index 67408f90f..8ec78e35c 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -56,6 +56,7 @@ struct git_config_file { int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_file *, const char *key); int (*foreach)(struct git_config_file *, const char *, int (*fn)(const git_config_entry *, void *), void *data); + int (*refresh)(struct git_config_file *); void (*free)(struct git_config_file *); }; @@ -134,19 +135,6 @@ GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length); GIT_EXTERN(int) git_config_open_default(git_config **out); /** - * 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 - */ -GIT_EXTERN(int) git_config_file__ondisk(struct git_config_file **out, const char *path); - -/** * Allocate a new configuration object * * This object is empty, so you have to add a file to it before you @@ -201,7 +189,8 @@ GIT_EXTERN(int) git_config_add_file( * @param force if a config file already exists for the given * priority level, replace it * @return 0 on success, GIT_EEXISTS when adding more than one file - * for a given priority level (and force_replace set to 0), or error code + * for a given priority level (and force_replace set to 0), + * GIT_ENOTFOUND when the file doesn't exist or error code */ GIT_EXTERN(int) git_config_add_file_ondisk( git_config *cfg, @@ -209,7 +198,6 @@ GIT_EXTERN(int) git_config_add_file_ondisk( unsigned int level, int force); - /** * Create a new config instance containing a single on-disk file * @@ -218,11 +206,12 @@ GIT_EXTERN(int) git_config_add_file_ondisk( * - git_config_new * - git_config_add_file_ondisk * - * @param cfg The configuration instance to create + * @param out The configuration instance to create * @param path Path to the on-disk file to open - * @return 0 or an error code + * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist + * or an error code */ -GIT_EXTERN(int) git_config_open_ondisk(git_config **cfg, const char *path); +GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path); /** * Build a single-level focused config object from a multi-level one. @@ -243,6 +232,19 @@ GIT_EXTERN(int) git_config_open_level( unsigned int level); /** + * Reload changed config files + * + * A config file may be changed on disk out from under the in-memory + * config object. This function causes us to look for files that have + * been modified since we last loaded them and refresh the config with + * the latest information. + * + * @param cfg The configuration to refresh + * @return 0 or an error code + */ +GIT_EXTERN(int) git_config_refresh(git_config *cfg); + +/** * Free the configuration and its associated memory and files * * @param cfg the configuration to free @@ -260,7 +262,7 @@ GIT_EXTERN(void) git_config_free(git_config *cfg); * @param name the variable's name * @return 0 or an error code */ -GIT_EXTERN(int) git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name); +GIT_EXTERN(int) git_config_get_entry(const git_config_entry **out, git_config *cfg, const char *name); /** * Get the value of an integer config variable. diff --git a/include/git2/diff.h b/include/git2/diff.h index 1932db029..47bfa5f69 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -33,7 +33,7 @@ GIT_BEGIN_DECL * Flags for diff options. A combination of these flags can be passed * in via the `flags` value in the `git_diff_options`. */ -enum { +typedef enum { /** Normal diff, the default */ GIT_DIFF_NORMAL = 0, /** Reverse the sides of the diff */ @@ -86,7 +86,9 @@ enum { * mode set to tree. Note: the tree SHA will not be available. */ GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16), -}; + /** Ignore file mode changes */ + GIT_DIFF_IGNORE_FILEMODE = (1 << 17), +} git_diff_option_t; /** * Structure describing options about how the diff should be executed. @@ -95,7 +97,7 @@ enum { * values. Similarly, passing NULL for the options structure will * give the defaults. The default values are marked below. * - * - flags: a combination of the GIT_DIFF_... values above + * - flags: a combination of the git_diff_option_t values above * - context_lines: number of lines of context to show around diffs * - interhunk_lines: min lines between diff hunks to merge them * - old_prefix: "directory" to prefix to old file names (default "a") @@ -124,7 +126,7 @@ typedef struct git_diff_list git_diff_list; * Most of the flags are just for internal consumption by libgit2, * but some of them may be interesting to external users. */ -enum { +typedef enum { GIT_DIFF_FILE_VALID_OID = (1 << 0), /** `oid` value is known correct */ GIT_DIFF_FILE_FREE_PATH = (1 << 1), /** `path` is allocated memory */ GIT_DIFF_FILE_BINARY = (1 << 2), /** should be considered binary data */ @@ -132,7 +134,7 @@ enum { GIT_DIFF_FILE_FREE_DATA = (1 << 4), /** internal file data is allocated */ GIT_DIFF_FILE_UNMAP_DATA = (1 << 5), /** internal file data is mmap'ed */ GIT_DIFF_FILE_NO_DATA = (1 << 6), /** file data should not be loaded */ -}; +} git_diff_file_flag_t; /** * What type of change is described by a git_diff_delta? @@ -218,7 +220,7 @@ typedef int (*git_diff_hunk_fn)( * output callbacks to demarcate lines that are actually part of * the file or hunk headers. */ -enum { +typedef enum { /* These values will be sent to `git_diff_data_fn` along with the line */ GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', @@ -233,7 +235,7 @@ enum { GIT_DIFF_LINE_FILE_HDR = 'F', GIT_DIFF_LINE_HUNK_HDR = 'H', GIT_DIFF_LINE_BINARY = 'B' -}; +} git_diff_line_t; /** * When iterating over a diff, callback that will be made per text diff @@ -259,6 +261,46 @@ typedef int (*git_diff_data_fn)( */ typedef struct git_diff_patch git_diff_patch; +/** + * Flags to control the behavior of diff rename/copy detection. + */ +typedef enum { + /** look for renames? (`--find-renames`) */ + GIT_DIFF_FIND_RENAMES = (1 << 0), + /** consider old size of modified for renames? (`--break-rewrites=N`) */ + GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1 << 1), + + /** look for copies? (a la `--find-copies`) */ + GIT_DIFF_FIND_COPIES = (1 << 2), + /** consider unmodified as copy sources? (`--find-copies-harder`) */ + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3), + + /** split large rewrites into delete/add pairs (`--break-rewrites=/M`) */ + GIT_DIFF_FIND_AND_BREAK_REWRITES = (1 << 4), +} git_diff_find_t; + +/** + * Control behavior of rename and copy detection + */ +typedef struct { + /** Combination of git_diff_find_t values (default FIND_RENAMES) */ + unsigned int flags; + + /** Similarity to consider a file renamed (default 50) */ + unsigned int rename_threshold; + /** Similarity of modified to be eligible rename source (default 50) */ + unsigned int rename_from_rewrite_threshold; + /** Similarity to consider a file a copy (default 50) */ + unsigned int copy_threshold; + /** Similarity to split modify into delete/add pair (default 60) */ + unsigned int break_rewrite_threshold; + + /** Maximum similarity sources to examine (a la diff's `-l` option or + * the `diff.renameLimit` config) (default 200) + */ + unsigned int target_limit; +} git_diff_find_options; + /** @name Diff List Generator Functions * @@ -277,52 +319,56 @@ GIT_EXTERN(void) git_diff_list_free(git_diff_list *diff); * * This is equivalent to `git diff <treeish> <treeish>` * + * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository containing the trees. - * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from. * @param new_tree A git_tree object to diff to. - * @param diff A pointer to a git_diff_list pointer that will be allocated. + * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_tree_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, git_tree *new_tree, - git_diff_list **diff); + const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Compute a difference between a tree and the index. + * Compute a difference between a tree and the repository index. * * This is equivalent to `git diff --cached <treeish>` or if you pass * the HEAD tree, then like `git diff --cached`. * + * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository containing the tree and index. - * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from. - * @param diff A pointer to a git_diff_list pointer that will be allocated. + * @param index The index to diff with; repo index used if NULL. + * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_index_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, - git_diff_list **diff); + git_index *index, + const git_diff_options *opts); /**< can be NULL for defaults */ /** - * Compute a difference between the working directory and the index. + * Compute a difference between the working directory and the repository index. * * This matches the `git diff` command. See the note below on * `git_diff_workdir_to_tree` for a discussion of the difference between * `git diff` and `git diff HEAD` and how to emulate a `git diff <treeish>` * using libgit2. * + * @param diff Output pointer to a git_diff_list pointer to be allocated. * @param repo The repository. + * @param index The index to diff from; repo index used if NULL. * @param opts Structure with options to influence diff or NULL for defaults. - * @param diff A pointer to a git_diff_list pointer that will be allocated. */ GIT_EXTERN(int) git_diff_workdir_to_index( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ - git_diff_list **diff); + git_index *index, + const git_diff_options *opts); /**< can be NULL for defaults */ /** * Compute a difference between the working directory and a tree. @@ -346,16 +392,16 @@ GIT_EXTERN(int) git_diff_workdir_to_index( * The tree-to-workdir diff for that file is 'modified', but core git would * show status 'deleted' since there is a pending deletion in the index. * + * @param diff A pointer to a git_diff_list pointer that will be allocated. * @param repo The repository containing the tree. - * @param opts Structure with options to influence diff or NULL for defaults. * @param old_tree A git_tree object to diff from. - * @param diff A pointer to a git_diff_list pointer that will be allocated. + * @param opts Structure with options to influence diff or NULL for defaults. */ GIT_EXTERN(int) git_diff_workdir_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, - git_diff_list **diff); + const git_diff_options *opts); /**< can be NULL for defaults */ /** * Merge one diff list into another. @@ -374,6 +420,22 @@ GIT_EXTERN(int) git_diff_merge( git_diff_list *onto, const git_diff_list *from); +/** + * Transform a diff list marking file renames, copies, etc. + * + * This modifies a diff list in place, replacing old entries that look + * like renames or copies with new entries reflecting those changes. + * This also will, if requested, break modified files into add/remove + * pairs if the amount of change is above a threshold. + * + * @param diff Diff list to run detection algorithms on + * @param options Control how detection should be run, NULL for defaults + * @return 0 on success, -1 on failure + */ +GIT_EXTERN(int) git_diff_find_similar( + git_diff_list *diff, + git_diff_find_options *options); + /**@}*/ @@ -603,6 +665,34 @@ GIT_EXTERN(int) git_diff_patch_get_line_in_hunk( size_t hunk_idx, size_t line_of_hunk); +/** + * Serialize the patch to text via callback. + * + * Returning a non-zero value from the callback will terminate the iteration + * and cause this return `GIT_EUSER`. + * + * @param patch A git_diff_patch representing changes to one file + * @param cb_data Reference pointer that will be passed to your callbacks. + * @param print_cb Callback function to output lines of the patch. Will be + * called for file headers, hunk headers, and diff lines. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_diff_patch_print( + git_diff_patch *patch, + void *cb_data, + git_diff_data_fn print_cb); + +/** + * Get the content of a patch as a single diff text. + * + * @param string Allocated string; caller must free. + * @param patch A git_diff_patch representing changes to one file + * @return 0 on success, <0 on failure. + */ +GIT_EXTERN(int) git_diff_patch_to_str( + char **string, + git_diff_patch *patch); + /**@}*/ diff --git a/include/git2/errors.h b/include/git2/errors.h index 38b7fe0ae..45e04578d 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -28,6 +28,7 @@ enum { GIT_EUSER = -7, GIT_EBAREREPO = -8, GIT_EORPHANEDHEAD = -9, + GIT_EUNMERGED = -10, GIT_PASSTHROUGH = -30, GIT_ITEROVER = -31, @@ -58,6 +59,8 @@ typedef enum { GITERR_SSL, GITERR_SUBMODULE, GITERR_THREAD, + GITERR_STASH, + GITERR_CHECKOUT, } git_error_t; /** diff --git a/include/git2/index.h b/include/git2/index.h index 062932e1a..8e1a7e521 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -84,12 +84,12 @@ typedef struct git_index_entry { char *path; } git_index_entry; -/** Representation of an unmerged file entry in the index. */ -typedef struct git_index_entry_unmerged { +/** Representation of a resolve undo entry in the index. */ +typedef struct git_index_reuc_entry { unsigned int mode[3]; git_oid oid[3]; char *path; -} git_index_entry_unmerged; +} git_index_reuc_entry; /** Capabilities of system that affect index actions. */ enum { @@ -99,6 +99,12 @@ enum { GIT_INDEXCAP_FROM_OWNER = ~0u }; +/** @name Index File Functions + * + * These functions work on the index file itself. + */ +/**@{*/ + /** * Create a new bare Git index object as a memory representation * of the Git index file in 'index_path', without a repository @@ -120,13 +126,17 @@ enum { GIT_EXTERN(int) git_index_open(git_index **index, const char *index_path); /** - * Clear the contents (all the entries) of an index object. - * This clears the index object in memory; changes must be manually - * written to disk for them to take effect. + * Create an in-memory index object. * - * @param index an existing index object + * This index object cannot be read/written to the filesystem, + * but may be used to perform in-memory index operations. + * + * The index must be freed once it's no longer in use. + * + * @param index the pointer for the new index + * @return 0 or an error code */ -GIT_EXTERN(void) git_index_clear(git_index *index); +GIT_EXTERN(int) git_index_new(git_index **index); /** * Free an existing index object. @@ -136,6 +146,14 @@ GIT_EXTERN(void) git_index_clear(git_index *index); GIT_EXTERN(void) git_index_free(git_index *index); /** + * Get the repository this index relates to + * + * @param index The index + * @return A pointer to the repository + */ +GIT_EXTERN(git_repository *) git_index_owner(const git_index *index); + +/** * Read index capabilities flags. * * @param index An existing index object @@ -175,44 +193,130 @@ GIT_EXTERN(int) git_index_read(git_index *index); GIT_EXTERN(int) git_index_write(git_index *index); /** - * Find the first index of any entries which point to given - * path in the Git index. + * Read a tree into the index file with stats + * + * The current index contents will be replaced by the specified tree. * * @param index an existing index object - * @param path path to search - * @return an index >= 0 if found, -1 otherwise + * @param tree tree to read + * @return 0 or an error code */ -GIT_EXTERN(int) git_index_find(git_index *index, const char *path); +GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree); + +/** + * Write the index as a tree + * + * This method will scan the index and write a representation + * of its current state back to disk; it recursively creates + * tree objects for each of the subtrees stored in the index, + * but only returns the OID of the root tree. This is the OID + * that can be used e.g. to create a commit. + * + * The index instance cannot be bare, and needs to be associated + * to an existing repository. + * + * The index must not contain any file in conflict. + * + * @param oid Pointer where to store the OID of the written tree + * @param index Index to write + * @return 0 on success, GIT_EUNMERGED when the index is not clean + * or an error code + */ +GIT_EXTERN(int) git_index_write_tree(git_oid *oid, git_index *index); + +/** + * Write the index as a tree to the given repository + * + * This method will do the same as `git_index_write_tree`, but + * letting the user choose the repository where the tree will + * be written. + * + * The index must not contain any file in conflict. + * + * @param oid Pointer where to store OID of the the written tree + * @param index Index to write + * @param repo Repository where to write the tree + * @return 0 on success, GIT_EUNMERGED when the index is not clean + * or an error code + */ +GIT_EXTERN(int) git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo); + +/**@}*/ + +/** @name Raw Index Entry Functions + * + * These functions work on index entries, and allow for raw manipulation + * of the entries. + */ +/**@{*/ + +/* Index entry manipulation */ /** - * Remove all entries with equal path except last added + * Get the count of entries currently in the index * * @param index an existing index object + * @return integer of count of current entries */ -GIT_EXTERN(void) git_index_uniq(git_index *index); +GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index); /** - * Add or update an index entry from a file in disk + * Clear the contents (all the entries) of an index object. + * This clears the index object in memory; changes must be manually + * written to disk for them to take effect. * - * The file `path` must be relative to the repository's - * working folder and must be readable. + * @param index an existing index object + */ +GIT_EXTERN(void) git_index_clear(git_index *index); + +/** + * Get a pointer to one of the entries in the index * - * This method will fail in bare index instances. + * The values of this entry can be modified (except the path) + * and the changes will be written back to disk on the next + * write() call. * - * This forces the file to be added to the index, not looking - * at gitignore rules. Those rules can be evaluated through - * the git_status APIs (in status.h) before calling this. + * The entry should not be freed by the caller. * * @param index an existing index object - * @param path filename to add - * @param stage stage for the entry + * @param n the position of the entry + * @return a pointer to the entry; NULL if out of bounds + */ +GIT_EXTERN(git_index_entry *) git_index_get_byindex(git_index *index, size_t n); + +/** + * Get a pointer to one of the entries in the index + * + * The values of this entry can be modified (except the path) + * and the changes will be written back to disk on the next + * write() call. + * + * The entry should not be freed by the caller. + * + * @param index an existing index object + * @param path path to search + * @param stage stage to search + * @return a pointer to the entry; NULL if it was not found + */ +GIT_EXTERN(git_index_entry *) git_index_get_bypath(git_index *index, const char *path, int stage); + +/** + * Remove an entry from the index + * + * @param index an existing index object + * @param path path to search + * @param stage stage to search * @return 0 or an error code */ -GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); +GIT_EXTERN(int) git_index_remove(git_index *index, const char *path, int stage); /** * Add or update an index entry from an in-memory struct * + * If a previous index entry exists that has the same path and stage + * as the given 'source_entry', it will be replaced. Otherwise, the + * 'source_entry' will be added. + * * A full copy (including the 'path' string) of the given * 'source_entry' will be inserted on the index. * @@ -220,133 +324,214 @@ GIT_EXTERN(int) git_index_add(git_index *index, const char *path, int stage); * @param source_entry new entry object * @return 0 or an error code */ -GIT_EXTERN(int) git_index_add2(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_entry); /** - * Add (append) an index entry from a file in disk + * Return the stage number from a git index entry * - * A new entry will always be inserted into the index; - * if the index already contains an entry for such - * path, the old entry will **not** be replaced. + * This entry is calculated from the entry's flag + * attribute like this: + * + * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT + * + * @param entry The entry + * @returns the stage number + */ +GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); + +/**@}*/ + +/** @name Workdir Index Entry Functions + * + * These functions work on index entries specifically in the working + * directory (ie, stage 0). + */ +/**@{*/ + +/** + * Add or update an index entry from a file in disk * * The file `path` must be relative to the repository's * working folder and must be readable. * * This method will fail in bare index instances. * + * This forces the file to be added to the index, not looking + * at gitignore rules. Those rules can be evaluated through + * the git_status APIs (in status.h) before calling this. + * + * If this file currently is the result of a merge conflict, this + * file will no longer be marked as conflicting. The data about + * the conflict will be moved to the "resolve undo" (REUC) section. + * * @param index an existing index object * @param path filename to add - * @param stage stage for the entry * @return 0 or an error code */ -GIT_EXTERN(int) git_index_append(git_index *index, const char *path, int stage); +GIT_EXTERN(int) git_index_add_from_workdir(git_index *index, const char *path); /** - * Add (append) an index entry from an in-memory struct + * Find the first index of any entries which point to given + * path in the Git index. + * + * @param index an existing index object + * @param path path to search + * @return an index >= 0 if found, -1 otherwise + */ +GIT_EXTERN(int) git_index_find(git_index *index, const char *path); + +/**@}*/ + +/** @name Conflict Index Entry Functions * - * A new entry will always be inserted into the index; - * if the index already contains an entry for the path - * in the `entry` struct, the old entry will **not** be - * replaced. + * These functions work on conflict index entries specifically (ie, stages 1-3) + */ +/**@{*/ + +/** + * Add or update index entries to represent a conflict * - * A full copy (including the 'path' string) of the given - * 'source_entry' will be inserted on the index. + * The entries are the entries from the tree included in the merge. Any + * entry may be null to indicate that that file was not present in the + * trees during the merge. For example, ancestor_entry may be NULL to + * indicate that a file was added in both branches and must be resolved. * * @param index an existing index object - * @param source_entry new entry object + * @param ancestor_entry the entry data for the ancestor of the conflict + * @param our_entry the entry data for our side of the merge conflict + * @param their_entry the entry data for their side of the merge conflict * @return 0 or an error code */ -GIT_EXTERN(int) git_index_append2(git_index *index, const git_index_entry *source_entry); +GIT_EXTERN(int) git_index_conflict_add(git_index *index, + const git_index_entry *ancestor_entry, + const git_index_entry *our_entry, + const git_index_entry *their_entry); /** - * Remove an entry from the index + * Get the index entries that represent a conflict of a single file. + * + * The values of this entry can be modified (except the paths) + * and the changes will be written back to disk on the next + * write() call. * + * @param ancestor_out Pointer to store the ancestor entry + * @param our_out Pointer to store the our entry + * @param their_out Pointer to store the their entry * @param index an existing index object - * @param position position of the entry to remove - * @return 0 or an error code + * @param path path to search */ -GIT_EXTERN(int) git_index_remove(git_index *index, int position); +GIT_EXTERN(int) git_index_conflict_get(git_index_entry **ancestor_out, git_index_entry **our_out, git_index_entry **their_out, git_index *index, const char *path); +/** + * Removes the index entries that represent a conflict of a single file. + * + * @param index an existing index object + * @param path to search + */ +GIT_EXTERN(int) git_index_conflict_remove(git_index *index, const char *path); /** - * Get a pointer to one of the entries in the index + * Remove all conflicts in the index (entries with a stage greater than 0.) * - * This entry can be modified, and the changes will be written - * back to disk on the next write() call. + * @param index an existing index object + */ +GIT_EXTERN(void) git_index_conflict_cleanup(git_index *index); + +/** + * Determine if the index contains entries representing file conflicts. * - * The entry should not be freed by the caller. + * @return 1 if at least one conflict is found, 0 otherwise. + */ +GIT_EXTERN(int) git_index_has_conflicts(git_index *index); + +/**@}*/ + +/** @name Resolve Undo (REUC) index entry manipulation. * - * @param index an existing index object - * @param n the position of the entry - * @return a pointer to the entry; NULL if out of bounds + * These functions work on the Resolve Undo index extension and contains + * data about the original files that led to a merge conflict. */ -GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, size_t n); +/**@{*/ /** - * Get the count of entries currently in the index + * Get the count of resolve undo entries currently in the index. * * @param index an existing index object - * @return integer of count of current entries + * @return integer of count of current resolve undo entries */ -GIT_EXTERN(unsigned int) git_index_entrycount(git_index *index); +GIT_EXTERN(unsigned int) git_index_reuc_entrycount(git_index *index); /** - * Get the count of unmerged entries currently in the index + * Finds the resolve undo entry that points to the given path in the Git + * index. * * @param index an existing index object - * @return integer of count of current unmerged entries + * @param path path to search + * @return an index >= 0 if found, -1 otherwise */ -GIT_EXTERN(unsigned int) git_index_entrycount_unmerged(git_index *index); +GIT_EXTERN(int) git_index_reuc_find(git_index *index, const char *path); /** - * Get an unmerged entry from the index. + * Get a resolve undo entry from the index. * * The returned entry is read-only and should not be modified * of freed by the caller. * * @param index an existing index object * @param path path to search - * @return the unmerged entry; NULL if not found + * @return the resolve undo entry; NULL if not found */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_index *index, const char *path); +GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_bypath(git_index *index, const char *path); /** - * Get an unmerged entry from the index. + * Get a resolve undo entry from the index. * * The returned entry is read-only and should not be modified * of freed by the caller. * * @param index an existing index object * @param n the position of the entry - * @return a pointer to the unmerged entry; NULL if out of bounds + * @return a pointer to the resolve undo entry; NULL if out of bounds */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, size_t n); +GIT_EXTERN(const git_index_reuc_entry *) git_index_reuc_get_byindex(git_index *index, size_t n); /** - * Return the stage number from a git index entry + * Adds an resolve undo entry for a file based on the given parameters. * - * This entry is calculated from the entry's flag - * attribute like this: + * The resolve undo entry contains the OIDs of files that were involved + * in a merge conflict after the conflict has been resolved. This allows + * conflicts to be re-resolved later. * - * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT + * If there exists a resolve undo entry for the given path in the index, + * it will be removed. * - * @param entry The entry - * @returns the stage number + * This method will fail in bare index instances. + * + * @param index an existing index object + * @param path filename to add + * @param ancestor_mode mode of the ancestor file + * @param ancestor_oid oid of the ancestor file + * @param our_mode mode of our file + * @param our_oid oid of our file + * @param their_mode mode of their file + * @param their_oid oid of their file + * @return 0 or an error code */ -GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); +GIT_EXTERN(int) git_index_reuc_add(git_index *index, const char *path, + int ancestor_mode, git_oid *ancestor_oid, + int our_mode, git_oid *our_oid, + int their_mode, git_oid *their_oid); /** - * Read a tree into the index file with stats - * - * The current index contents will be replaced by the specified tree. The total - * node count is collected in stats. + * Remove an resolve undo entry from the index * * @param index an existing index object - * @param tree tree to read - * @param stats structure that receives the total node count (may be NULL) + * @param position position of the resolve undo entry to remove * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats); +GIT_EXTERN(int) git_index_reuc_remove(git_index *index, int position); + +/**@}*/ /** @} */ GIT_END_DECL diff --git a/include/git2/indexer.h b/include/git2/indexer.h index 87f48fe27..a2a155473 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -16,13 +16,19 @@ GIT_BEGIN_DECL * This is passed as the first argument to the callback to allow the * user to see the progress. */ -typedef struct git_indexer_stats { - unsigned int total; - unsigned int processed; - unsigned int received; -} git_indexer_stats; +typedef struct git_transfer_progress { + unsigned int total_objects; + unsigned int indexed_objects; + unsigned int received_objects; + size_t received_bytes; +} git_transfer_progress; +/** + * Type for progress callbacks during indexing + */ +typedef void (*git_transfer_progress_callback)(const git_transfer_progress *stats, void *payload); + typedef struct git_indexer git_indexer; typedef struct git_indexer_stream git_indexer_stream; @@ -31,8 +37,14 @@ typedef struct git_indexer_stream git_indexer_stream; * * @param out where to store the indexer instance * @param path to the directory where the packfile should be stored + * @param progress_cb function to call with progress information + * @param progress_payload payload for the progress callback */ -GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *path); +GIT_EXTERN(int) git_indexer_stream_new( + git_indexer_stream **out, + const char *path, + git_transfer_progress_callback progress_cb, + void *progress_callback_payload); /** * Add data to the indexer @@ -42,7 +54,7 @@ GIT_EXTERN(int) git_indexer_stream_new(git_indexer_stream **out, const char *pat * @param size the size of the data * @param stats stat storage */ -GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats); /** * Finalize the pack and index @@ -51,7 +63,7 @@ GIT_EXTERN(int) git_indexer_stream_add(git_indexer_stream *idx, const void *data * * @param idx the indexer */ -GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats); /** * Get the packfile's hash @@ -88,7 +100,7 @@ GIT_EXTERN(int) git_indexer_new(git_indexer **out, const char *packname); * @param idx the indexer instance * @param stats storage for the running state */ -GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_indexer_stats *stats); +GIT_EXTERN(int) git_indexer_run(git_indexer *idx, git_transfer_progress *stats); /** * Write the index file to disk. diff --git a/include/git2/odb.h b/include/git2/odb.h index c6e73571b..4afa3b788 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -11,6 +11,7 @@ #include "types.h" #include "oid.h" #include "odb_backend.h" +#include "indexer.h" /** * @file git2/odb.h @@ -263,6 +264,26 @@ GIT_EXTERN(int) git_odb_open_wstream(git_odb_stream **stream, git_odb *db, size_ GIT_EXTERN(int) git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oid); /** + * Open a stream for writing a pack file to the ODB. + * + * If the ODB layer understands pack files, then the given + * packfile will likely be streamed directly to disk (and a + * corresponding index created). If the ODB layer does not + * understand pack files, the objects will be stored in whatever + * format the ODB layer uses. + * + * @see git_odb_writepack + * + * @param writepack pointer to the writepack functions + * @param db object database where the stream will read from + * @param progress_cb function to call with progress information. + * Be aware that this is called inline with network and indexing operations, + * so performance may be affected. + * @param progress_payload payload for the progress callback + */ +GIT_EXTERN(int) git_odb_write_pack(git_odb_writepack **writepack, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload); + +/** * Determine the object-ID (sha1 hash) of a data buffer * * The resulting SHA-1 OID will be the identifier for the data diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index cb8069787..4df48d77e 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -10,6 +10,7 @@ #include "common.h" #include "types.h" #include "oid.h" +#include "indexer.h" /** * @file git2/backend.h @@ -21,6 +22,7 @@ GIT_BEGIN_DECL struct git_odb_stream; +struct git_odb_writepack; /** An instance for a custom backend */ struct git_odb_backend { @@ -75,11 +77,16 @@ struct git_odb_backend { struct git_odb_backend *, const git_oid *); - int (*foreach)( - struct git_odb_backend *, - int (*cb)(git_oid *oid, void *data), - void *data - ); + int (* foreach)( + struct git_odb_backend *, + int (*cb)(git_oid *oid, void *data), + void *data); + + int (* writepack)( + struct git_odb_writepack **, + struct git_odb_backend *, + git_transfer_progress_callback progress_cb, + void *progress_payload); void (* free)(struct git_odb_backend *); }; @@ -102,6 +109,15 @@ struct git_odb_stream { void (*free)(struct git_odb_stream *stream); }; +/** A stream to write a pack file to the ODB */ +struct git_odb_writepack { + struct git_odb_backend *backend; + + int (*add)(struct git_odb_writepack *writepack, const void *data, size_t size, git_transfer_progress *stats); + int (*commit)(struct git_odb_writepack *writepack, git_transfer_progress *stats); + void (*free)(struct git_odb_writepack *writepack); +}; + GIT_EXTERN(int) git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir); GIT_EXTERN(int) git_odb_backend_loose(git_odb_backend **backend_out, const char *objects_dir, int compression_level, int do_fsync); GIT_EXTERN(int) git_odb_backend_one_pack(git_odb_backend **backend_out, const char *index_file); diff --git a/include/git2/pack.h b/include/git2/pack.h index 748ad2e11..94d5fc6a1 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -78,6 +78,30 @@ GIT_EXTERN(int) git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid * GIT_EXTERN(int) git_packbuilder_write(git_packbuilder *pb, const char *file); /** + * Create the new pack and pass each object to the callback + * + * @param pb the packbuilder + * @param cb the callback to call with each packed object's buffer + * @param data the callback's data + * @return 0 or an error code + */ +GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *data), void *data); + +/** + * Get the total number of objects the packbuilder will write out + * + * @param pb the packbuilder + */ +GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); + +/** + * Get the number of objects the packbuilder has already written out + * + * @param pb the packbuilder + */ +GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb); + +/** * Free the packbuilder and all associated data * * @param pb The packbuilder diff --git a/include/git2/reflog.h b/include/git2/reflog.h index 447915ef8..72e1f1753 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -88,8 +88,12 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog); /** * Lookup an entry by its index * + * Requesting the reflog entry with an index of 0 (zero) will + * return the most recently created entry. + * * @param reflog a previously loaded reflog - * @param idx the position to lookup + * @param idx the position of the entry to lookup. Should be greater than or + * equal to 0 (zero) and less than `git_reflog_entrycount()`. * @return the entry; NULL if not found */ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx); @@ -97,21 +101,23 @@ GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog /** * Remove an entry from the reflog by its index * - * To ensure there's no gap in the log history, set the `rewrite_previosu_entry` to 1. - * When deleting entry `n`, member old_oid of entry `n-1` (if any) will be updated with - * the value of memeber new_oid of entry `n+1`. + * To ensure there's no gap in the log history, set `rewrite_previous_entry` + * param value to 1. When deleting entry `n`, member old_oid of entry `n-1` + * (if any) will be updated with the value of member new_oid of entry `n+1`. * * @param reflog a previously loaded reflog. * - * @param idx the position of the entry to remove. + * @param idx the position of the entry to remove. Should be greater than or + * equal to 0 (zero) and less than `git_reflog_entrycount()`. * * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise. * - * @return 0 on success or an error code. + * @return 0 on success, GIT_ENOTFOUND if the entry doesn't exist + * or an error code. */ GIT_EXTERN(int) git_reflog_drop( git_reflog *reflog, - unsigned int idx, + size_t idx, int rewrite_previous_entry); /** diff --git a/include/git2/refs.h b/include/git2/refs.h index 001c2bcc7..bc3f44482 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -22,13 +22,16 @@ GIT_BEGIN_DECL /** - * Lookup a reference by its name in a repository. + * Lookup a reference by name in a repository. * - * The generated reference must be freed by the user. + * The returned reference must be freed by the user. + * + * See `git_reference_create_symbolic()` for documentation about valid + * reference names. * * @param reference_out pointer to the looked-up reference * @param repo the repository to look up the reference - * @param name the long name for the reference (e.g. HEAD, ref/heads/master, refs/tags/v0.1.0, ...) + * @param name the long name for the reference (e.g. HEAD, refs/heads/master, refs/tags/v0.1.0, ...) * @return 0 or an error code */ GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_repository *repo, const char *name); @@ -36,6 +39,10 @@ GIT_EXTERN(int) git_reference_lookup(git_reference **reference_out, git_reposito /** * Lookup a reference by name and resolve immediately to OID. * + * This function provides a quick way to resolve a reference name straight + * through to the object id that it refers to. This avoids having to + * allocate or free any `git_reference` objects for simple situations. + * * @param oid Pointer to oid to be filled in * @param repo The repository in which to look up the reference * @param name The long name for the reference @@ -47,13 +54,24 @@ GIT_EXTERN(int) git_reference_name_to_oid( /** * Create a new symbolic reference. * - * The reference will be created in the repository and written - * to the disk. + * A symbolic reference is a reference name that refers to another + * reference name. If the other name moves, the symbolic name will move, + * too. As a simple example, the "HEAD" reference might refer to + * "refs/heads/master" while on the "master" branch of a repository. + * + * The symbolic reference will be created in the repository and written to + * the disk. The generated reference object must be freed by the user. + * + * Valid reference names must follow one of two patterns: * - * The generated reference must be freed by the user. + * 1. Top-level names must contain only capital letters and underscores, + * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). + * 2. Names prefixed with "refs/" can be almost anything. You must avoid + * the characters '~', '^', ':', '\\', '?', '[', and '*', and the + * sequences ".." and "@{" which have special meaning to revparse. * - * If `force` is true and there already exists a reference - * with the same name, it will be overwritten. + * This function will return an error if a reference already exists with the + * given name unless `force` is true, in which case it will be overwritten. * * @param ref_out Pointer to the newly created reference * @param repo Repository where that reference will live @@ -65,15 +83,27 @@ GIT_EXTERN(int) git_reference_name_to_oid( GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repository *repo, const char *name, const char *target, int force); /** - * Create a new object id reference. + * Create a new direct reference. * - * The reference will be created in the repository and written - * to the disk. + * A direct reference (also called an object id reference) refers directly + * to a specific object id (a.k.a. OID or SHA) in the repository. The id + * permanently refers to the object (although the reference itself can be + * moved). For example, in libgit2 the direct ref "refs/tags/v0.17.0" + * refers to OID 5b9fac39d8a76b9139667c26a63e6b3f204b3977. * - * The generated reference must be freed by the user. + * The direct reference will be created in the repository and written to + * the disk. The generated reference object must be freed by the user. * - * If `force` is true and there already exists a reference - * with the same name, it will be overwritten. + * Valid reference names must follow one of two patterns: + * + * 1. Top-level names must contain only capital letters and underscores, + * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). + * 2. Names prefixed with "refs/" can be almost anything. You must avoid + * the characters '~', '^', ':', '\\', '?', '[', and '*', and the + * sequences ".." and "@{" which have special meaning to revparse. + * + * This function will return an error if a reference already exists with the + * given name unless `force` is true, in which case it will be overwritten. * * @param ref_out Pointer to the newly created reference * @param repo Repository where that reference will live @@ -85,9 +115,14 @@ GIT_EXTERN(int) git_reference_create_symbolic(git_reference **ref_out, git_repos GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository *repo, const char *name, const git_oid *id, int force); /** - * Get the OID pointed to by a reference. + * Get the OID pointed to by a direct reference. + * + * Only available if the reference is direct (i.e. an object id reference, + * not a symbolic one). * - * Only available if the reference is direct (i.e. not symbolic) + * To find the OID of a symbolic ref, call `git_reference_resolve()` and + * then this function (or maybe use `git_reference_name_to_oid()` to + * directly resolve a reference name all the way through to an OID). * * @param ref The reference * @return a pointer to the oid if available, NULL otherwise @@ -95,9 +130,9 @@ GIT_EXTERN(int) git_reference_create_oid(git_reference **ref_out, git_repository GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref); /** - * Get full name to the reference pointed by this reference + * Get full name to the reference pointed to by a symbolic reference. * - * Only available if the reference is symbolic + * Only available if the reference is symbolic. * * @param ref The reference * @return a pointer to the name if available, NULL otherwise @@ -105,7 +140,7 @@ GIT_EXTERN(const git_oid *) git_reference_oid(git_reference *ref); GIT_EXTERN(const char *) git_reference_target(git_reference *ref); /** - * Get the type of a reference + * Get the type of a reference. * * Either direct (GIT_REF_OID) or symbolic (GIT_REF_SYMBOLIC) * @@ -115,7 +150,9 @@ GIT_EXTERN(const char *) git_reference_target(git_reference *ref); GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref); /** - * Get the full name of a reference + * Get the full name of a reference. + * + * See `git_reference_create_symbolic()` for rules about valid names. * * @param ref The reference * @return the full name for the ref @@ -123,18 +160,16 @@ GIT_EXTERN(git_ref_t) git_reference_type(git_reference *ref); GIT_EXTERN(const char *) git_reference_name(git_reference *ref); /** - * Resolve a symbolic reference + * Resolve a symbolic reference to a direct reference. * - * This method iteratively peels a symbolic reference - * until it resolves to a direct reference to an OID. + * This method iteratively peels a symbolic reference until it resolves to + * a direct reference to an OID. * - * The peeled reference is returned in the `resolved_ref` - * argument, and must be freed manually once it's no longer - * needed. + * The peeled reference is returned in the `resolved_ref` argument, and + * must be freed manually once it's no longer needed. * - * If a direct reference is passed as an argument, - * a copy of that reference is returned. This copy must - * be manually freed too. + * If a direct reference is passed as an argument, a copy of that + * reference is returned. This copy must be manually freed too. * * @param resolved_ref Pointer to the peeled reference * @param ref The reference @@ -143,7 +178,7 @@ GIT_EXTERN(const char *) git_reference_name(git_reference *ref); GIT_EXTERN(int) git_reference_resolve(git_reference **resolved_ref, git_reference *ref); /** - * Get the repository where a reference resides + * Get the repository where a reference resides. * * @param ref The reference * @return a pointer to the repo @@ -153,11 +188,9 @@ GIT_EXTERN(git_repository *) git_reference_owner(git_reference *ref); /** * Set the symbolic target of a reference. * - * The reference must be a symbolic reference, otherwise - * this method will fail. + * The reference must be a symbolic reference, otherwise this will fail. * - * The reference will be automatically updated in - * memory and on disk. + * The reference will be automatically updated in memory and on disk. * * @param ref The reference * @param target The new target for the reference @@ -168,11 +201,9 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target) /** * Set the OID target of a reference. * - * The reference must be a direct reference, otherwise - * this method will fail. + * The reference must be a direct reference, otherwise this will fail. * - * The reference will be automatically updated in - * memory and on disk. + * The reference will be automatically updated in memory and on disk. * * @param ref The reference * @param id The new target OID for the reference @@ -181,7 +212,7 @@ GIT_EXTERN(int) git_reference_set_target(git_reference *ref, const char *target) GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); /** - * Rename an existing reference + * Rename an existing reference. * * This method works for both direct and symbolic references. * The new name will be checked for validity and may be @@ -189,8 +220,7 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); * * The given git_reference will be updated in place. * - * The reference will be immediately renamed in-memory - * and on disk. + * The reference will be immediately renamed in-memory and on disk. * * If the `force` flag is not enabled, and there's already * a reference with the given name, the renaming will fail. @@ -209,12 +239,12 @@ GIT_EXTERN(int) git_reference_set_oid(git_reference *ref, const git_oid *id); GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, int force); /** - * Delete an existing reference + * Delete an existing reference. * * This method works for both direct and symbolic references. * - * The reference will be immediately removed on disk and from - * memory. The given reference pointer will no longer be valid. + * The reference will be immediately removed on disk and from memory + * (i.e. freed). The given reference pointer will no longer be valid. * * @param ref The reference to remove * @return 0 or an error code @@ -222,7 +252,7 @@ GIT_EXTERN(int) git_reference_rename(git_reference *ref, const char *new_name, i GIT_EXTERN(int) git_reference_delete(git_reference *ref); /** - * Pack all the loose references in the repository + * Pack all the loose references in the repository. * * This method will load into the cache all the loose * references on the repository and update the @@ -237,44 +267,42 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); GIT_EXTERN(int) git_reference_packall(git_repository *repo); /** - * Fill a list with all the references that can be found - * in a repository. + * Fill a list with all the references that can be found in a repository. * - * The listed references may be filtered by type, or using - * a bitwise OR of several types. Use the magic value - * `GIT_REF_LISTALL` to obtain all references, including - * packed ones. + * Using the `list_flags` parameter, the listed references may be filtered + * by type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of + * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`. + * For convenience, use the value `GIT_REF_LISTALL` to obtain all + * references, including packed ones. * - * The string array will be filled with the names of all - * references; these values are owned by the user and - * should be free'd manually when no longer needed, using - * `git_strarray_free`. + * The string array will be filled with the names of all references; these + * values are owned by the user and should be free'd manually when no + * longer needed, using `git_strarray_free()`. * * @param array Pointer to a git_strarray structure where * the reference names will be stored * @param repo Repository where to find the refs - * @param list_flags Filtering flags for the reference - * listing. + * @param list_flags Filtering flags for the reference listing * @return 0 or an error code */ GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, unsigned int list_flags); /** - * Perform an operation on each reference in the repository + * Perform a callback on each reference in the repository. * - * The processed references may be filtered by type, or using - * a bitwise OR of several types. Use the magic value - * `GIT_REF_LISTALL` to obtain all references, including - * packed ones. + * Using the `list_flags` parameter, the references may be filtered by + * type (`GIT_REF_OID` or `GIT_REF_SYMBOLIC`) or using a bitwise OR of + * `git_ref_t` values. To include packed refs, include `GIT_REF_PACKED`. + * For convenience, use the value `GIT_REF_LISTALL` to obtain all + * references, including packed ones. * - * The `callback` function will be called for each of the references - * in the repository, and will receive the name of the reference and - * the `payload` value passed to this method. Returning a non-zero - * value from the callback will terminate the iteration. + * The `callback` function will be called for each reference in the + * repository, receiving the name of the reference and the `payload` value + * passed to this method. Returning a non-zero value from the callback + * will terminate the iteration. * * @param repo Repository where to find the refs - * @param list_flags Filtering flags for the reference - * listing. + * @param list_flags Filtering flags for the reference listing. * @param callback Function which will be called for every listed ref * @param payload Additional data to pass to the callback * @return 0 on success, GIT_EUSER on non-zero callback, or error code @@ -282,7 +310,7 @@ GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, un GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload); /** - * Check if a reference has been loaded from a packfile + * Check if a reference has been loaded from a packfile. * * @param ref A git reference * @return 0 in case it's not packed; 1 otherwise @@ -290,19 +318,17 @@ GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_fl GIT_EXTERN(int) git_reference_is_packed(git_reference *ref); /** - * Reload a reference from disk + * Reload a reference from disk. * - * Reference pointers may become outdated if the Git - * repository is accessed simultaneously by other clients - * while the library is open. + * Reference pointers can become outdated if the Git repository is + * accessed simultaneously by other clients while the library is open. * - * This method forces a reload of the reference from disk, - * to ensure that the provided information is still - * reliable. + * This method forces a reload of the reference from disk, to ensure that + * the provided information is still reliable. * - * If the reload fails (e.g. the reference no longer exists - * on disk, or has become corrupted), an error code will be - * returned and the reference pointer will be invalidated. + * If the reload fails (e.g. the reference no longer exists on disk, or + * has become corrupted), an error code will be returned and the reference + * pointer will be invalidated and freed. * * @param ref The reference to reload * @return 0 on success, or an error code @@ -310,7 +336,7 @@ GIT_EXTERN(int) git_reference_is_packed(git_reference *ref); GIT_EXTERN(int) git_reference_reload(git_reference *ref); /** - * Free the given reference + * Free the given reference. * * @param ref git_reference */ @@ -326,36 +352,30 @@ GIT_EXTERN(void) git_reference_free(git_reference *ref); GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2); /** - * Loop over all the references and issue a callback for each one - * which name matches the given glob pattern. - * - * The processed references may be filtered by type, or using - * a bitwise OR of several types. Use the magic value - * `GIT_REF_LISTALL` to obtain all references, including - * packed ones. - * - * @param repo Repository where to find the references. + * Perform a callback on each reference in the repository whose name + * matches the given pattern. * - * @param glob Glob pattern references should match. + * This function acts like `git_reference_foreach()` with an additional + * pattern match being applied to the reference name before issuing the + * callback function. See that function for more information. * - * @param list_flags Filtering flags for the reference - * listing. + * The pattern is matched using fnmatch or "glob" style where a '*' matches + * any sequence of letters, a '?' matches any letter, and square brackets + * can be used to define character ranges (such as "[0-9]" for digits). * - * @param callback Callback to invoke per found reference. - * - * @param payload Extra parameter to callback function. - * - * @return 0 or an error code. + * @param repo Repository where to find the refs + * @param glob Pattern to match (fnmatch-style) against reference name. + * @param list_flags Filtering flags for the reference listing. + * @param callback Function which will be called for every listed ref + * @param payload Additional data to pass to the callback + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_reference_foreach_glob( - git_repository *repo, - const char *glob, - unsigned int list_flags, - int (*callback)( - const char *reference_name, - void *payload), - void *payload -); + git_repository *repo, + const char *glob, + unsigned int list_flags, + int (*callback)(const char *reference_name, void *payload), + void *payload); /** * Check if a reflog exists for the specified reference. @@ -387,7 +407,8 @@ GIT_EXTERN(int) git_reference_is_branch(git_reference *ref); */ GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); -enum { + +typedef enum { GIT_REF_FORMAT_NORMAL = 0, /** @@ -406,27 +427,25 @@ enum { * (e.g., foo/<star>/bar but not foo/bar<star>). */ GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1), -}; +} git_reference_normalize_t; /** - * Normalize the reference name by removing any leading - * slash (/) characters and collapsing runs of adjacent slashes - * between name components into a single slash. - * - * Once normalized, if the reference name is valid, it will be - * returned in the user allocated buffer. - * - * @param buffer_out The user allocated buffer where the - * normalized name will be stored. - * - * @param buffer_size buffer_out size - * - * @param name name to be checked. - * - * @param flags Flags to determine the options to be applied while - * checking the validatity of the name. - * - * @return 0 or an error code. + * Normalize reference name and check validity. + * + * This will normalize the reference name by removing any leading slash + * '/' characters and collapsing runs of adjacent slashes between name + * components into a single slash. + * + * Once normalized, if the reference name is valid, it will be returned in + * the user allocated buffer. + * + * @param buffer_out User allocated buffer to store normalized name + * @param buffer_size Size of buffer_out + * @param name Reference name to be checked. + * @param flags Flags to constrain name validation rules - see the + * GIT_REF_FORMAT constants above. + * @return 0 on success or error code (GIT_EBUFS if buffer is too small, -1 + * if reference is invalid) */ GIT_EXTERN(int) git_reference_normalize_name( char *buffer_out, @@ -435,8 +454,7 @@ GIT_EXTERN(int) git_reference_normalize_name( unsigned int flags); /** - * Recursively peel an reference until an object of the - * specified type is met. + * Recursively peel reference until object of the specified type is found. * * The retrieved `peeled` object is owned by the repository * and should be closed with the `git_object_free` method. @@ -457,12 +475,18 @@ GIT_EXTERN(int) git_reference_peel( /** * Ensure the reference name is well-formed. * - * @param refname name to be checked. + * Valid reference names must follow one of two patterns: + * + * 1. Top-level names must contain only capital letters and underscores, + * and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). + * 2. Names prefixed with "refs/" can be almost anything. You must avoid + * the characters '~', '^', ':', '\\', '?', '[', and '*', and the + * sequences ".." and "@{" which have special meaning to revparse. * + * @param refname name to be checked. * @return 1 if the reference name is acceptable; 0 if it isn't */ -GIT_EXTERN(int) git_reference_is_valid_name( - const char *refname); +GIT_EXTERN(int) git_reference_is_valid_name(const char *refname); /** @} */ GIT_END_DECL diff --git a/include/git2/remote.h b/include/git2/remote.h index 6471acc6a..44390e7a4 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -13,6 +13,7 @@ #include "net.h" #include "indexer.h" #include "strarray.h" +#include "transport.h" /** * @file git2/remote.h @@ -50,7 +51,7 @@ GIT_EXTERN(int) git_remote_new(git_remote **out, git_repository *repo, const cha * Get the information for a particular remote * * @param out pointer to the new remote object - * @param cfg the repository's configuration + * @param repo the associated repository * @param name the remote's name * @return 0 or an error code */ @@ -167,8 +168,9 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, int direction); * If you a return a non-zero value from the callback, this will stop * looping over the refs. * - * @param refs where to store the refs * @param remote the remote + * @param list_cb function to call with each ref discovered at the remote + * @param payload additional data to pass to the callback * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload); @@ -183,10 +185,16 @@ GIT_EXTERN(int) git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void * filename will be NULL and the function will return success. * * @param remote the remote to download from - * @param filename where to store the temporary filename + * @param progress_cb function to call with progress information. Be aware that + * this is called inline with network and indexing operations, so performance + * may be affected. + * @param progress_payload payload for the progress callback * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); +GIT_EXTERN(int) git_remote_download( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload); /** * Check whether the remote is connected @@ -194,6 +202,7 @@ GIT_EXTERN(int) git_remote_download(git_remote *remote, git_off_t *bytes, git_in * Check whether the remote's underlying transport is connected to the * remote host. * + * @param remote the remote * @return 1 if it's connected, 0 otherwise. */ GIT_EXTERN(int) git_remote_connected(git_remote *remote); @@ -203,6 +212,8 @@ GIT_EXTERN(int) git_remote_connected(git_remote *remote); * * At certain points in its operation, the network code checks whether * the operation has been cancelled and if so stops the operation. + * + * @param remote the remote */ GIT_EXTERN(void) git_remote_stop(git_remote *remote); @@ -230,7 +241,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote); * Update the tips to the new state * * @param remote the remote to update - * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value + * @return 0 or an error code */ GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); @@ -268,6 +279,7 @@ GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo * @param repo the repository in which to create the remote * @param name the remote's name * @param url the remote's url + * @return 0 or an error code */ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url); @@ -277,10 +289,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha * @param remote the remote to configure * @param check whether to check the server's certificate (defaults to yes) */ - GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); /** + * Set a credentials acquisition callback for this remote. If the remote is + * not available for anonymous access, then you must set this callback in order + * to provide credentials to the transport at the time of authentication + * failure so that retry can be performed. + * + * @param remote the remote to configure + * @param cred_acquire_cb The credentials acquisition callback to use (defaults + * to NULL) + */ +GIT_EXTERN(void) git_remote_set_cred_acquire_cb( + git_remote *remote, + git_cred_acquire_cb cred_acquire_cb); + +/** + * Sets a custom transport for the remote. The caller can use this function + * to bypass the automatic discovery of a transport by URL scheme (i.e. + * http://, https://, git://) and supply their own transport to be used + * instead. After providing the transport to a remote using this function, + * the transport object belongs exclusively to that remote, and the remote will + * free it when it is freed with git_remote_free. + * + * @param remote the remote to configure + * @param transport the transport object for the remote to use + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_set_transport( + git_remote *remote, + git_transport *transport); + +/** * Argument to the completion callback which tells it which operation * finished. */ @@ -313,6 +354,11 @@ struct git_remote_callbacks { */ GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks); +/** + * Get the statistics structure that is filled in by the fetch operation. + */ +GIT_EXTERN(const git_transfer_progress *) git_remote_stats(git_remote *remote); + enum { GIT_REMOTE_DOWNLOAD_TAGS_UNSET, GIT_REMOTE_DOWNLOAD_TAGS_NONE, @@ -336,6 +382,41 @@ GIT_EXTERN(int) git_remote_autotag(git_remote *remote); */ GIT_EXTERN(void) git_remote_set_autotag(git_remote *remote, int value); +/** + * Give the remote a new name + * + * All remote-tracking branches and configuration settings + * for the remote are updated. + * + * @param remote the remote to rename + * @param new_name the new name the remote should bear + * @param callback Optional callback to notify the consumer of fetch refspecs + * that haven't been automatically updated and need potential manual tweaking. + * @param payload Additional data to pass to the callback + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_rename( + git_remote *remote, + const char *new_name, + int (*callback)(const char *problematic_refspec, void *payload), + void *payload); + +/** + * Retrieve the update FETCH_HEAD setting. + * + * @param remote the remote to query + * @return the update FETCH_HEAD setting + */ +GIT_EXTERN(int) git_remote_update_fetchhead(git_remote *remote); + +/** + * Sets the update FETCH_HEAD setting. By default, FETCH_HEAD will be + * updated on every fetch. Set to 0 to disable. + * + * @param remote the remote to configure + * @param value 0 to disable updating FETCH_HEAD + */ +GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value); /** @} */ GIT_END_DECL diff --git a/include/git2/repository.h b/include/git2/repository.h index 193ac9523..f891e91e9 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -273,7 +273,7 @@ GIT_EXTERN(int) git_repository_init_ext( * @param repo a repository object * * @return 0 on success, GIT_EORPHANEDHEAD when HEAD points to a non existing - * branch, an error code otherwise + * branch, GIT_ENOTFOUND when HEAD is missing; an error code otherwise */ GIT_EXTERN(int) git_repository_head(git_reference **head_out, git_repository *repo); @@ -305,7 +305,7 @@ GIT_EXTERN(int) git_repository_head_orphan(git_repository *repo); * Check if a repository is empty * * An empty repository has just been initialized and contains - * no commits. + * no references. * * @param repo Repo to test * @return 1 if the repository is empty, 0 if it isn't, error code @@ -569,6 +569,28 @@ GIT_EXTERN(int) git_repository_set_head_detached( GIT_EXTERN(int) git_repository_detach_head( git_repository* repo); +typedef enum { + GIT_REPOSITORY_STATE_NONE, + GIT_REPOSITORY_STATE_MERGE, + GIT_REPOSITORY_STATE_REVERT, + GIT_REPOSITORY_STATE_CHERRY_PICK, + GIT_REPOSITORY_STATE_BISECT, + GIT_REPOSITORY_STATE_REBASE, + GIT_REPOSITORY_STATE_REBASE_INTERACTIVE, + GIT_REPOSITORY_STATE_REBASE_MERGE, + GIT_REPOSITORY_STATE_APPLY_MAILBOX, + GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE, +} git_repository_state_t; + +/** + * Determines the status of a git repository - ie, whether an operation + * (merge, cherry-pick, etc) is in progress. + * + * @param repo Repository pointer + * @return The state of the repository + */ +GIT_EXTERN(int) git_repository_state(git_repository *repo); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/stash.h b/include/git2/stash.h new file mode 100644 index 000000000..3ecd9e88d --- /dev/null +++ b/include/git2/stash.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_stash_h__ +#define INCLUDE_git_stash_h__ + +#include "common.h" +#include "types.h" + +/** + * @file git2/stash.h + * @brief Git stash management routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_STASH_DEFAULT = 0, + + /* All changes already added to the index + * are left intact in the working directory + */ + GIT_STASH_KEEP_INDEX = (1 << 0), + + /* All untracked files are also stashed and then + * cleaned up from the working directory + */ + GIT_STASH_INCLUDE_UNTRACKED = (1 << 1), + + /* All ignored files are also stashed and then + * cleaned up from the working directory + */ + GIT_STASH_INCLUDE_IGNORED = (1 << 2), +}; + +/** + * Save the local modifications to a new stash. + * + * @param out Object id of the commit containing the stashed state. + * This commit is also the target of the direct reference refs/stash. + * + * @param repo The owning repository. + * + * @param stasher The identity of the person performing the stashing. + * + * @param message Optional description along with the stashed state. + * + * @param flags Flags to control the stashing process. + * + * @return 0 on success, GIT_ENOTFOUND where there's nothing to stash, + * or error code. + */ + +GIT_EXTERN(int) git_stash_save( + git_oid *out, + git_repository *repo, + git_signature *stasher, + const char *message, + uint32_t flags); + +/** + * When iterating over all the stashed states, callback that will be + * issued per entry. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @param message The stash message. + * + * @param stash_oid The commit oid of the stashed state. + * + * @param payload Extra parameter to callback function. + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +typedef int (*stash_cb)( + size_t index, + const char* message, + const git_oid *stash_oid, + void *payload); + +/** + * Loop over all the stashed states and issue a callback for each one. + * + * If the callback returns a non-zero value, this will stop looping. + * + * @param repo Repository where to find the stash. + * + * @param callabck Callback to invoke per found stashed state. The most recent + * stash state will be enumerated first. + * + * @param payload Extra parameter to callback function. + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_stash_foreach( + git_repository *repo, + stash_cb callback, + void *payload); + +/** + * Remove a single stashed state from the stash list. + * + * @param repo The owning repository. + * + * @param index The position within the stash list. 0 points to the + * most recent stashed state. + * + * @return 0 on success, or error code + */ + +GIT_EXTERN(int) git_stash_drop( + git_repository *repo, + size_t index); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/status.h b/include/git2/status.h index 979e6e4ff..8c59d768d 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -19,6 +19,16 @@ */ GIT_BEGIN_DECL +/** + * Status flags for a single file. + * + * A combination of these values will be returned to indicate the status of + * a file. Status compares the working directory, the index, and the + * current HEAD of the repository. The `GIT_STATUS_INDEX` set of flags + * represents the status of file in the index relative to the HEAD, and the + * `GIT_STATUS_WT` set of flags represent the status of the file in the + * working directory relative to the index. + */ typedef enum { GIT_STATUS_CURRENT = 0, @@ -39,12 +49,16 @@ typedef enum { /** * Gather file statuses and run a callback for each one. * - * The callback is passed the path of the file, the status and the data - * pointer passed to this function. If the callback returns something other - * than 0, this function will stop looping and return GIT_EUSER. + * The callback is passed the path of the file, the status (a combination of + * the `git_status_t` values above) and the `payload` data pointer passed + * into this function. + * + * If the callback returns a non-zero value, this function will stop looping + * and return GIT_EUSER. * - * @param repo a repository object - * @param callback the function to call on each file + * @param repo A repository object + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_status_foreach( @@ -53,7 +67,7 @@ GIT_EXTERN(int) git_status_foreach( void *payload); /** - * Select the files on which to report status. + * For extended status, select the files on which to report status. * * - GIT_STATUS_SHOW_INDEX_AND_WORKDIR is the default. This is the * rough equivalent of `git status --porcelain` where each file @@ -81,40 +95,55 @@ typedef enum { /** * Flags to control status callbacks * - * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should - * be made on untracked files. These will only be made if the - * workdir files are included in the status "show" option. - * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should - * get callbacks. Again, these callbacks will only be made if - * the workdir files are included in the status "show" option. - * Right now, there is no option to include all files in - * directories that are ignored completely. - * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback - * should be made even on unmodified files. - * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories - * which appear to be submodules should just be skipped over. - * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the - * contents of untracked directories should be included in the - * status. Normally if an entire directory is new, then just - * the top-level directory will be included (with a trailing - * slash on the entry name). Given this flag, the directory - * itself will not be included, but all the files in it will. - * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given - * path will be treated as a literal path, and not as a pathspec. + * - GIT_STATUS_OPT_INCLUDE_UNTRACKED says that callbacks should be made + * on untracked files. These will only be made if the workdir files are + * included in the status "show" option. + * - GIT_STATUS_OPT_INCLUDE_IGNORED says that ignored files should get + * callbacks. Again, these callbacks will only be made if the workdir + * files are included in the status "show" option. Right now, there is + * no option to include all files in directories that are ignored + * completely. + * - GIT_STATUS_OPT_INCLUDE_UNMODIFIED indicates that callback should be + * made even on unmodified files. + * - GIT_STATUS_OPT_EXCLUDE_SUBMODULES indicates that directories which + * appear to be submodules should just be skipped over. + * - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS indicates that the contents of + * untracked directories should be included in the status. Normally if + * an entire directory is new, then just the top-level directory will be + * included (with a trailing slash on the entry name). Given this flag, + * the directory itself will not be included, but all the files in it + * will. + * - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given path + * will be treated as a literal path, and not as a pathspec. + * + * Calling `git_status_foreach()` is like calling the extended version + * with: GIT_STATUS_OPT_INCLUDE_IGNORED, GIT_STATUS_OPT_INCLUDE_UNTRACKED, + * and GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS. */ - -enum { +typedef enum { GIT_STATUS_OPT_INCLUDE_UNTRACKED = (1 << 0), GIT_STATUS_OPT_INCLUDE_IGNORED = (1 << 1), GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2), GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3), GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4), GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5), -}; +} git_status_opt_t; /** - * Options to control how callbacks will be made by - * `git_status_foreach_ext()`. + * Options to control how `git_status_foreach_ext()` will issue callbacks. + * + * This structure is set so that zeroing it out will give you relatively + * sane defaults. + * + * The `show` value is one of the `git_status_show_t` constants that + * control which files to scan and in what order. + * + * The `flags` value is an OR'ed combination of the `git_status_opt_t` + * values above. + * + * The `pathspec` is an array of path patterns to match (using + * fnmatch-style matching), or just an array of paths to match exactly if + * `GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH` is specified in the flags. */ typedef struct { git_status_show_t show; @@ -124,6 +153,17 @@ typedef struct { /** * Gather file status information and run callbacks as requested. + * + * This is an extended version of the `git_status_foreach()` API that + * allows for more granular control over which paths will be processed and + * in what order. See the `git_status_options` structure for details + * about the additional controls that this makes available. + * + * @param repo Repository object + * @param opts Status options structure + * @param callback The function to call on each file + * @param payload Pointer to pass through to callback function + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_status_foreach_ext( git_repository *repo, @@ -132,14 +172,17 @@ GIT_EXTERN(int) git_status_foreach_ext( void *payload); /** - * Get file status for a single file - * - * @param status_flags the status value - * @param repo a repository object - * @param path the file to retrieve status for, rooted at the repo's workdir - * @return GIT_EINVALIDPATH when `path` points at a folder, GIT_ENOTFOUND when - * the file doesn't exist in any of HEAD, the index or the worktree, - * 0 otherwise + * Get file status for a single file. + * + * This is not quite the same as calling `git_status_foreach_ext()` with + * the pathspec set to the specified path. + * + * @param status_flags The status value for the file + * @param repo A repository object + * @param path The file to retrieve status for, rooted at the repo's workdir + * @return 0 on success, GIT_ENOTFOUND if the file is not found in the HEAD, + * index, and work tree, GIT_EINVALIDPATH if `path` points at a folder, + * GIT_EAMBIGUOUS if "path" matches multiple files, -1 on other error. */ GIT_EXTERN(int) git_status_file( unsigned int *status_flags, @@ -156,9 +199,9 @@ GIT_EXTERN(int) git_status_file( * One way to think of this is if you were to do "git add ." on the * directory containing the file, would it be added or not? * - * @param ignored boolean returning 0 if the file is not ignored, 1 if it is - * @param repo a repository object - * @param path the file to check ignores for, rooted at the repo's workdir. + * @param ignored Boolean returning 0 if the file is not ignored, 1 if it is + * @param repo A repository object + * @param path The file to check ignores for, rooted at the repo's workdir. * @return 0 if ignore rules could be processed for the file (regardless * of whether it exists or not), or an error < 0 if they could not. */ diff --git a/include/git2/tag.h b/include/git2/tag.h index 5602914f7..a1b685f12 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -104,7 +104,7 @@ GIT_EXTERN(const git_oid *) git_tag_target_oid(git_tag *tag); * @param tag a previously loaded tag. * @return type of the tagged object */ -GIT_EXTERN(git_otype) git_tag_type(git_tag *tag); +GIT_EXTERN(git_otype) git_tag_target_type(git_tag *tag); /** * Get the name of a tag diff --git a/include/git2/threads.h b/include/git2/threads.h index 567a10487..f448f6a4d 100644 --- a/include/git2/threads.h +++ b/include/git2/threads.h @@ -27,8 +27,10 @@ GIT_BEGIN_DECL * * If libgit2 has been built without GIT_THREADS * support, this function is a no-op. + * + * @return 0 or an error code */ -GIT_EXTERN(void) git_threads_init(void); +GIT_EXTERN(int) git_threads_init(void); /** * Shutdown the threading system. diff --git a/include/git2/transport.h b/include/git2/transport.h new file mode 100644 index 000000000..b2bdaae71 --- /dev/null +++ b/include/git2/transport.h @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_transport_h__ +#define INCLUDE_git_transport_h__ + +#include "indexer.h" +#include "net.h" + +/** + * @file git2/transport.h + * @brief Git transport interfaces and functions + * @defgroup git_transport interfaces and functions + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/* + *** Begin interface for credentials acquisition *** + */ + +typedef enum { + /* git_cred_userpass_plaintext */ + GIT_CREDTYPE_USERPASS_PLAINTEXT = 1, +} git_credtype_t; + +/* The base structure for all credential types */ +typedef struct git_cred { + git_credtype_t credtype; + void (*free)( + struct git_cred *cred); +} git_cred; + +/* A plaintext username and password */ +typedef struct git_cred_userpass_plaintext { + git_cred parent; + char *username; + char *password; +} git_cred_userpass_plaintext; + +/** + * Creates a new plain-text username and password credential object. + * + * @param cred The newly created credential object. + * @param username The username of the credential. + * @param password The password of the credential. + */ +GIT_EXTERN(int) git_cred_userpass_plaintext_new( + git_cred **cred, + const char *username, + const char *password); + +/** + * Signature of a function which acquires a credential object. + * + * @param cred The newly created credential object. + * @param url The resource for which we are demanding a credential. + * @param allowed_types A bitmask stating which cred types are OK to return. + */ +typedef int (*git_cred_acquire_cb)( + git_cred **cred, + const char *url, + int allowed_types); + +/* + *** End interface for credentials acquisition *** + *** Begin base transport interface *** + */ + +typedef enum { + GIT_TRANSPORTFLAGS_NONE = 0, + /* If the connection is secured with SSL/TLS, the authenticity + * of the server certificate should not be verified. */ + GIT_TRANSPORTFLAGS_NO_CHECK_CERT = 1 +} git_transport_flags_t; + +typedef void (*git_transport_message_cb)(const char *str, int len, void *data); + +typedef struct git_transport { + /* Set progress and error callbacks */ + int (*set_callbacks)(struct git_transport *transport, + git_transport_message_cb progress_cb, + git_transport_message_cb error_cb, + void *payload); + + /* Connect the transport to the remote repository, using the given + * direction. */ + int (*connect)(struct git_transport *transport, + const char *url, + git_cred_acquire_cb cred_acquire_cb, + int direction, + int flags); + + /* This function may be called after a successful call to connect(). The + * provided callback is invoked for each ref discovered on the remote + * end. */ + int (*ls)(struct git_transport *transport, + git_headlist_cb list_cb, + void *payload); + + /* Reserved until push is implemented. */ + int (*push)(struct git_transport *transport); + + /* This function may be called after a successful call to connect(), when + * the direction is FETCH. The function performs a negotiation to calculate + * the wants list for the fetch. */ + int (*negotiate_fetch)(struct git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + + /* This function may be called after a successful call to negotiate_fetch(), + * when the direction is FETCH. This function retrieves the pack file for + * the fetch from the remote end. */ + int (*download_pack)(struct git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload); + + /* Checks to see if the transport is connected */ + int (*is_connected)(struct git_transport *transport, int *connected); + + /* Reads the flags value previously passed into connect() */ + int (*read_flags)(struct git_transport *transport, int *flags); + + /* Cancels any outstanding transport operation */ + void (*cancel)(struct git_transport *transport); + + /* This function is the reverse of connect() -- it terminates the + * connection to the remote end. */ + int (*close)(struct git_transport *transport); + + /* Frees/destructs the git_transport object. */ + void (*free)(struct git_transport *transport); +} git_transport; + +/** + * Function to use to create a transport from a URL. The transport database + * is scanned to find a transport that implements the scheme of the URI (i.e. + * git:// or http://) and a transport object is returned to the caller. + * + * @param transport The newly created transport (out) + * @param url The URL to connect to + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_new(git_transport **transport, const char *url); + +/** + * Function which checks to see if a transport could be created for the + * given URL (i.e. checks to see if libgit2 has a transport that supports + * the given URL's scheme) + * + * @param url The URL to check + * @return Zero if the URL is not valid; nonzero otherwise + */ +GIT_EXTERN(int) git_transport_valid_url(const char *url); + +/* Signature of a function which creates a transport */ +typedef int (*git_transport_cb)(git_transport **transport, void *param); + +/* Transports which come with libgit2 (match git_transport_cb). The expected + * value for "param" is listed in-line below. */ + +/** + * Create an instance of the dummy transport. + * + * @param transport The newly created transport (out) + * @param param You must pass NULL for this parameter. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_dummy( + git_transport **transport, + /* NULL */ void *param); + +/** + * Create an instance of the local transport. + * + * @param transport The newly created transport (out) + * @param param You must pass NULL for this parameter. + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_local( + git_transport **transport, + /* NULL */ void *param); + +/** + * Create an instance of the smart transport. + * + * @param transport The newly created transport (out) + * @param param A pointer to a git_smart_subtransport_definition + * @return 0 or an error code + */ +GIT_EXTERN(int) git_transport_smart( + git_transport **transport, + /* (git_smart_subtransport_definition *) */ void *param); + +/* + *** End of base transport interface *** + *** Begin interface for subtransports for the smart transport *** + */ + +/* The smart transport knows how to speak the git protocol, but it has no + * knowledge of how to establish a connection between it and another endpoint, + * or how to move data back and forth. For this, a subtransport interface is + * declared, and the smart transport delegates this work to the subtransports. + * Three subtransports are implemented: git, http, and winhttp. (The http and + * winhttp transports each implement both http and https.) */ + +/* Subtransports can either be RPC = 0 (persistent connection) or RPC = 1 + * (request/response). The smart transport handles the differences in its own + * logic. The git subtransport is RPC = 0, while http and winhttp are both + * RPC = 1. */ + +/* Actions that the smart transport can ask + * a subtransport to perform */ +typedef enum { + GIT_SERVICE_UPLOADPACK_LS = 1, + GIT_SERVICE_UPLOADPACK = 2, +} git_smart_service_t; + +struct git_smart_subtransport; + +/* A stream used by the smart transport to read and write data + * from a subtransport */ +typedef struct git_smart_subtransport_stream { + /* The owning subtransport */ + struct git_smart_subtransport *subtransport; + + int (*read)( + struct git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read); + + int (*write)( + struct git_smart_subtransport_stream *stream, + const char *buffer, + size_t len); + + void (*free)( + struct git_smart_subtransport_stream *stream); +} git_smart_subtransport_stream; + +/* An implementation of a subtransport which carries data for the + * smart transport */ +typedef struct git_smart_subtransport { + int (* action)( + git_smart_subtransport_stream **out, + struct git_smart_subtransport *transport, + const char *url, + git_smart_service_t action); + + void (* free)(struct git_smart_subtransport *transport); +} git_smart_subtransport; + +/* A function which creates a new subtransport for the smart transport */ +typedef int (*git_smart_subtransport_cb)( + git_smart_subtransport **out, + git_transport* owner); + +typedef struct git_smart_subtransport_definition { + /* The function to use to create the git_smart_subtransport */ + git_smart_subtransport_cb callback; + /* True if the protocol is stateless; false otherwise. For example, + * http:// is stateless, but git:// is not. */ + unsigned rpc : 1; +} git_smart_subtransport_definition; + +/* Smart transport subtransports that come with libgit2 */ + +/** + * Create an instance of the http subtransport. This subtransport + * also supports https. On Win32, this subtransport may be implemented + * using the WinHTTP library. + * + * @param out The newly created subtransport + * @param owner The smart transport to own this subtransport + * @return 0 or an error code + */ +GIT_EXTERN(int) git_smart_subtransport_http( + git_smart_subtransport **out, + git_transport* owner); + +/** + * Create an instance of the git subtransport. + * + * @param out The newly created subtransport + * @param owner The smart transport to own this subtransport + * @return 0 or an error code + */ +GIT_EXTERN(int) git_smart_subtransport_git( + git_smart_subtransport **out, + git_transport* owner); + +/* + *** End interface for subtransports for the smart transport *** + */ + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/tree.h b/include/git2/tree.h index 2ee1f4afa..527f81819 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -185,24 +185,6 @@ GIT_EXTERN(int) git_tree_entry_to_object( const git_tree_entry *entry); /** - * Write a tree to the ODB from the index file - * - * This method will scan the index and write a representation - * of its current state back to disk; it recursively creates - * tree objects for each of the subtrees stored in the index, - * but only returns the OID of the root tree. This is the OID - * that can be used e.g. to create a commit. - * - * The index instance cannot be bare, and needs to be associated - * to an existing repository. - * - * @param oid Pointer where to store the written tree - * @param index Index to write - * @return 0 or an error code - */ -GIT_EXTERN(int) git_tree_create_fromindex(git_oid *oid, git_index *index); - -/** * Create a new tree builder. * * The tree builder can be used to create or modify diff --git a/include/git2/types.h b/include/git2/types.h index 01ddbf3d6..58cbaecc5 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -89,6 +89,9 @@ typedef struct git_odb_object git_odb_object; /** A stream to read/write from the ODB */ typedef struct git_odb_stream git_odb_stream; +/** A stream to write a packfile to the ODB */ +typedef struct git_odb_writepack git_odb_writepack; + /** * Representation of an existing git repository, * including all its object contents diff --git a/src/attr.c b/src/attr.c index 025ad3c87..b5757446f 100644 --- a/src/attr.c +++ b/src/attr.c @@ -261,32 +261,26 @@ bool git_attr_cache__is_cached( static int load_attr_file( const char **data, - git_attr_file_stat_sig *sig, + git_futils_filestamp *stamp, const char *filename) { int error; git_buf content = GIT_BUF_INIT; - struct stat st; - if (p_stat(filename, &st) < 0) - return GIT_ENOTFOUND; + error = git_futils_filestamp_check(stamp, filename); + if (error < 0) + return error; - if (sig != NULL && - (git_time_t)st.st_mtime == sig->seconds && - (git_off_t)st.st_size == sig->size && - (unsigned int)st.st_ino == sig->ino) + /* if error == 0, then file is up to date. By returning GIT_ENOTFOUND, + * we tell the caller not to reparse this file... + */ + if (!error) return GIT_ENOTFOUND; - error = git_futils_readbuffer_updated(&content, filename, NULL, NULL); + error = git_futils_readbuffer(&content, filename); if (error < 0) return error; - if (sig != NULL) { - sig->seconds = (git_time_t)st.st_mtime; - sig->size = (git_off_t)st.st_size; - sig->ino = (unsigned int)st.st_ino; - } - *data = git_buf_detach(&content); return 0; @@ -307,7 +301,7 @@ static int load_attr_blob_from_index( (error = git_index_find(index, relfile)) < 0) return error; - entry = git_index_get(index, error); + entry = git_index_get_byindex(index, error); if (old_oid && git_oid_cmp(old_oid, &entry->oid) == 0) return GIT_ENOTFOUND; @@ -386,7 +380,7 @@ int git_attr_cache__push_file( git_attr_cache *cache = git_repository_attr_cache(repo); git_attr_file *file = NULL; git_blob *blob = NULL; - git_attr_file_stat_sig st; + git_futils_filestamp stamp; assert(filename && stack); @@ -408,12 +402,10 @@ int git_attr_cache__push_file( /* if not in cache, load data, parse, and cache */ if (source == GIT_ATTR_FILE_FROM_FILE) { - if (file) - memcpy(&st, &file->cache_data.st, sizeof(st)); - else - memset(&st, 0, sizeof(st)); + git_futils_filestamp_set( + &stamp, file ? &file->cache_data.stamp : NULL); - error = load_attr_file(&content, &st, filename); + error = load_attr_file(&content, &stamp, filename); } else { error = load_attr_blob_from_index(&content, &blob, repo, file ? &file->cache_data.oid : NULL, relfile); @@ -448,7 +440,7 @@ int git_attr_cache__push_file( if (blob) git_oid_cpy(&file->cache_data.oid, git_object_id((git_object *)blob)); else - memcpy(&file->cache_data.st, &st, sizeof(st)); + git_futils_filestamp_set(&file->cache_data.stamp, &stamp); finish: /* push file onto vector if we found one*/ diff --git a/src/attr_file.h b/src/attr_file.h index 877daf306..5bdfc7054 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -7,10 +7,12 @@ #ifndef INCLUDE_attr_file_h__ #define INCLUDE_attr_file_h__ +#include "git2/oid.h" #include "git2/attr.h" #include "vector.h" #include "pool.h" #include "buffer.h" +#include "fileops.h" #define GIT_ATTR_FILE ".gitattributes" #define GIT_ATTR_FILE_INREPO "info/attributes" @@ -54,19 +56,13 @@ typedef struct { } git_attr_assignment; typedef struct { - git_time_t seconds; - git_off_t size; - unsigned int ino; -} git_attr_file_stat_sig; - -typedef struct { char *key; /* cache "source#path" this was loaded from */ git_vector rules; /* vector of <rule*> or <fnmatch*> */ git_pool *pool; bool pool_is_allocated; union { git_oid oid; - git_attr_file_stat_sig st; + git_futils_filestamp stamp; } cache_data; } git_attr_file; diff --git a/src/branch.c b/src/branch.c index 991314508..c6173caca 100644 --- a/src/branch.c +++ b/src/branch.c @@ -92,6 +92,8 @@ cleanup: int git_branch_delete(git_reference *branch) { int is_head; + git_buf config_section = GIT_BUF_INIT; + int error = -1; assert(branch); @@ -110,7 +112,23 @@ int git_branch_delete(git_reference *branch) return -1; } - return git_reference_delete(branch); + if (git_buf_printf(&config_section, "branch.%s", 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; + + if (git_reference_delete(branch) < 0) + goto on_error; + + error = 0; + +on_error: + git_buf_free(&config_section); + return error; } typedef struct { @@ -161,7 +179,9 @@ int git_branch_move( const char *new_branch_name, int force) { - git_buf new_reference_name = GIT_BUF_INIT; + git_buf new_reference_name = GIT_BUF_INIT, + old_config_section = GIT_BUF_INIT, + new_config_section = GIT_BUF_INIT; int error; assert(branch && new_branch_name); @@ -172,10 +192,28 @@ int git_branch_move( if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) goto cleanup; - error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force); + if (git_buf_printf( + &old_config_section, + "branch.%s", + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) + goto cleanup; + + if ((error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force)) < 0) + goto cleanup; + + if (git_buf_printf(&new_config_section, "branch.%s", new_branch_name) < 0) + goto cleanup; + + if ((error = git_config_rename_section( + git_reference_owner(branch), + git_buf_cstr(&old_config_section), + git_buf_cstr(&new_config_section))) < 0) + goto cleanup; cleanup: git_buf_free(&new_reference_name); + git_buf_free(&old_config_section); + git_buf_free(&new_config_section); return error; } @@ -230,6 +268,11 @@ int git_branch_tracking( if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0) goto cleanup; + + if (remote_name == NULL || merge_name == NULL) { + error = GIT_ENOTFOUND; + goto cleanup; + } if (strcmp(".", remote_name) != 0) { if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0) @@ -274,7 +317,7 @@ int git_branch_is_head( error = git_repository_head(&head, git_reference_owner(branch)); - if (error == GIT_EORPHANEDHEAD) + if (error == GIT_EORPHANEDHEAD || error == GIT_ENOTFOUND) return false; if (error < 0) diff --git a/src/buffer.c b/src/buffer.c index b40b16b66..e55b0a230 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -549,3 +549,31 @@ void git_buf_unescape(git_buf *buf) { buf->size = git__unescape(buf->ptr); } + +int git_buf_splice( + git_buf *buf, + size_t where, + size_t nb_to_remove, + const char *data, + size_t nb_to_insert) +{ + assert(buf && + where <= git_buf_len(buf) && + where + nb_to_remove <= git_buf_len(buf)); + + /* Ported from git.git + * https://github.com/git/git/blob/16eed7c/strbuf.c#L159-176 + */ + if (git_buf_grow(buf, git_buf_len(buf) + nb_to_insert - nb_to_remove) < 0) + return -1; + + memmove(buf->ptr + where + nb_to_insert, + buf->ptr + where + nb_to_remove, + buf->size - where - nb_to_remove); + + memcpy(buf->ptr + where, data, nb_to_insert); + + buf->size = buf->size + nb_to_insert - nb_to_remove; + buf->ptr[buf->size] = '\0'; + return 0; +} diff --git a/src/buffer.h b/src/buffer.h index 2aae06c7c..a2896d486 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -158,4 +158,29 @@ void git_buf_unescape(git_buf *buf); /* Write data as base64 encoded in buffer */ int git_buf_put_base64(git_buf *buf, const char *data, 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 diff --git a/src/cache.c b/src/cache.c index 1f5b8872c..edd3a47dd 100644 --- a/src/cache.c +++ b/src/cache.c @@ -41,6 +41,7 @@ void git_cache_free(git_cache *cache) git_cached_obj_decref(cache->nodes[i], cache->free_obj); } + git_mutex_free(&cache->lock); git__free(cache->nodes); } diff --git a/src/checkout.c b/src/checkout.c index b56b459d2..eff14813d 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -21,19 +21,20 @@ #include "repository.h" #include "filter.h" #include "blob.h" +#include "diff.h" +#include "pathspec.h" -struct checkout_diff_data -{ +typedef struct { + git_repository *repo; + git_diff_list *diff; + git_checkout_opts *opts; git_buf *path; size_t workdir_len; - git_checkout_opts *checkout_opts; - git_indexer_stats *stats; - git_repository *owner; bool can_symlink; - bool found_submodules; - bool create_submodules; int error; -}; + size_t total_steps; + size_t completed_steps; +} checkout_diff_data; static int buffer_to_file( git_buf *buffer, @@ -42,20 +43,23 @@ static int buffer_to_file( int file_open_flags, mode_t file_mode) { - int fd, error, error_close; + int fd, error; if ((error = git_futils_mkpath2file(path, dir_mode)) < 0) return error; - if ((fd = p_open(path, file_open_flags, file_mode)) < 0) + if ((fd = p_open(path, file_open_flags, file_mode)) < 0) { + giterr_set(GITERR_OS, "Could not open '%s' for writing", path); return fd; + } - error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); - - error_close = p_close(fd); - - if (!error) - error = error_close; + if ((error = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer))) < 0) { + giterr_set(GITERR_OS, "Could not write to '%s'", path); + (void)p_close(fd); + } else { + if ((error = p_close(fd)) < 0) + giterr_set(GITERR_OS, "Error while closing '%s'", path); + } if (!error && (file_mode & 0100) != 0 && @@ -107,7 +111,8 @@ static int blob_content_to_file( if (!file_mode) file_mode = entry_filemode; - error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); + error = buffer_to_file( + &filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); cleanup: git_filters_free(&filters); @@ -118,7 +123,8 @@ cleanup: return error; } -static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) +static int blob_content_to_link( + git_blob *blob, const char *path, bool can_symlink) { git_buf linktarget = GIT_BUF_INIT; int error; @@ -137,39 +143,52 @@ static int blob_content_to_link(git_blob *blob, const char *path, bool can_symli } static int checkout_submodule( - struct checkout_diff_data *data, + checkout_diff_data *data, const git_diff_file *file) { + /* Until submodules are supported, UPDATE_ONLY means do nothing here */ + if ((data->opts->checkout_strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) + return 0; + if (git_futils_mkdir( - file->path, git_repository_workdir(data->owner), - data->checkout_opts->dir_mode, GIT_MKDIR_PATH) < 0) + file->path, git_repository_workdir(data->repo), + data->opts->dir_mode, GIT_MKDIR_PATH) < 0) return -1; - /* TODO: two cases: + /* TODO: Support checkout_strategy options. Two circumstances: * 1 - submodule already checked out, but we need to move the HEAD * to the new OID, or * 2 - submodule not checked out and we should recursively check it out * - * Checkout will not execute a pull request on the submodule, but a - * clone command should probably be able to. Do we need a submodule - * callback option? + * Checkout will not execute a pull on the submodule, but a clone + * command should probably be able to. Do we need a submodule callback? */ return 0; } +static void report_progress( + checkout_diff_data *data, + const char *path) +{ + if (data->opts->progress_cb) + data->opts->progress_cb( + path, data->completed_steps, data->total_steps, + data->opts->progress_payload); +} + static int checkout_blob( - struct checkout_diff_data *data, + checkout_diff_data *data, const git_diff_file *file) { + int error = 0; git_blob *blob; - int error; git_buf_truncate(data->path, data->workdir_len); - if (git_buf_joinpath(data->path, git_buf_cstr(data->path), file->path) < 0) + if (git_buf_puts(data->path, file->path) < 0) return -1; - if ((error = git_blob_lookup(&blob, data->owner, &file->oid)) < 0) + if ((error = git_blob_lookup(&blob, data->repo, &file->oid)) < 0) return error; if (S_ISLNK(file->mode)) @@ -177,145 +196,421 @@ static int checkout_blob( blob, git_buf_cstr(data->path), data->can_symlink); else error = blob_content_to_file( - blob, git_buf_cstr(data->path), file->mode, data->checkout_opts); + blob, git_buf_cstr(data->path), file->mode, data->opts); git_blob_free(blob); return error; } -static int checkout_remove_the_old( - void *cb_data, const git_diff_delta *delta, float progress) +static int retrieve_symlink_caps(git_repository *repo, bool *can_symlink) { - struct checkout_diff_data *data = cb_data; - git_checkout_opts *opts = data->checkout_opts; + git_config *cfg; + int error; - GIT_UNUSED(progress); - data->stats->processed++; + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; - if ((delta->status == GIT_DELTA_UNTRACKED && - (opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) || - (delta->status == GIT_DELTA_TYPECHANGE && - (opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0)) - { - data->error = git_futils_rmdir_r( - delta->new_file.path, - git_repository_workdir(data->owner), - GIT_DIRREMOVAL_FILES_AND_DIRS); + error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); + + /* If "core.symlinks" is not found anywhere, default to true. */ + if (error == GIT_ENOTFOUND) { + *can_symlink = true; + error = 0; } - return data->error; + return error; } -static int checkout_create_the_new( - void *cb_data, const git_diff_delta *delta, float progress) +static void normalize_options( + git_checkout_opts *normalized, git_checkout_opts *proposed) { - int error = 0; - struct checkout_diff_data *data = cb_data; - git_checkout_opts *opts = data->checkout_opts; - bool do_checkout = false, do_notify = false; + assert(normalized); - GIT_UNUSED(progress); - data->stats->processed++; + if (!proposed) + memset(normalized, 0, sizeof(git_checkout_opts)); + else + memmove(normalized, proposed, sizeof(git_checkout_opts)); - if (delta->status == GIT_DELTA_MODIFIED || - delta->status == GIT_DELTA_TYPECHANGE) - { - if ((opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED) != 0) - do_checkout = true; - else if (opts->skipped_notify_cb != NULL) - do_notify = !data->create_submodules; + /* implied checkout strategies */ + if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_MODIFIED) != 0 || + (normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0) + normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_UNMODIFIED; + + if ((normalized->checkout_strategy & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0) + normalized->checkout_strategy |= GIT_CHECKOUT_UPDATE_MISSING; + + /* opts->disable_filters is false by default */ + + if (!normalized->dir_mode) + normalized->dir_mode = GIT_DIR_MODE; + + if (!normalized->file_open_flags) + normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; +} + +enum { + CHECKOUT_ACTION__NONE = 0, + CHECKOUT_ACTION__REMOVE = 1, + CHECKOUT_ACTION__UPDATE_BLOB = 2, + CHECKOUT_ACTION__UPDATE_SUBMODULE = 4, + CHECKOUT_ACTION__CONFLICT = 8, + CHECKOUT_ACTION__MAX = 8 +}; + +static int checkout_confirm_update_blob( + checkout_diff_data *data, + const git_diff_delta *delta, + int action) +{ + int error; + unsigned int strat = data->opts->checkout_strategy; + struct stat st; + bool update_only = ((strat & GIT_CHECKOUT_UPDATE_ONLY) != 0); + + /* for typechange, remove the old item first */ + if (delta->status == GIT_DELTA_TYPECHANGE) { + if (update_only) + action = CHECKOUT_ACTION__NONE; + else + action |= CHECKOUT_ACTION__REMOVE; + + return action; } - else if (delta->status == GIT_DELTA_DELETED && - (opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING) != 0) - do_checkout = true; - if (do_notify) { - if (opts->skipped_notify_cb( - delta->old_file.path, &delta->old_file.oid, - delta->old_file.mode, opts->notify_payload)) - { - giterr_clear(); - error = GIT_EUSER; + git_buf_truncate(data->path, data->workdir_len); + if (git_buf_puts(data->path, delta->new_file.path) < 0) + return -1; + + if ((error = p_lstat_posixly(git_buf_cstr(data->path), &st)) < 0) { + if (errno == ENOENT) { + if (update_only) + action = CHECKOUT_ACTION__NONE; + } else if (errno == ENOTDIR) { + /* File exists where a parent dir needs to go - i.e. untracked + * typechange. Ignore if UPDATE_ONLY, remove if allowed. + */ + if (update_only) + action = CHECKOUT_ACTION__NONE; + else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0) + action |= CHECKOUT_ACTION__REMOVE; + else + action = CHECKOUT_ACTION__CONFLICT; } + /* otherwise let error happen when we attempt blob checkout later */ + } + else if (S_ISDIR(st.st_mode)) { + /* Directory exists where a blob needs to go - i.e. untracked + * typechange. Ignore if UPDATE_ONLY, remove if allowed. + */ + if (update_only) + action = CHECKOUT_ACTION__NONE; + else if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) != 0) + action |= CHECKOUT_ACTION__REMOVE; + else + action = CHECKOUT_ACTION__CONFLICT; } - if (do_checkout) { - bool is_submodule = S_ISGITLINK(delta->old_file.mode); + return action; +} - if (is_submodule) - data->found_submodules = true; +static int checkout_action_for_delta( + checkout_diff_data *data, + const git_diff_delta *delta, + const git_index_entry *head_entry) +{ + int action = CHECKOUT_ACTION__NONE; + unsigned int strat = data->opts->checkout_strategy; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + if (!head_entry) { + /* file independently created in wd, even though not in HEAD */ + if ((strat & GIT_CHECKOUT_UPDATE_MISSING) == 0) + action = CHECKOUT_ACTION__CONFLICT; + } + else if (!git_oid_equal(&head_entry->oid, &delta->old_file.oid)) { + /* working directory was independently updated to match index */ + if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0) + action = CHECKOUT_ACTION__CONFLICT; + } + break; + + case GIT_DELTA_ADDED: + /* Impossible. New files should be UNTRACKED or TYPECHANGE */ + action = CHECKOUT_ACTION__CONFLICT; + break; + + case GIT_DELTA_DELETED: + if (head_entry && /* working dir missing, but exists in HEAD */ + (strat & GIT_CHECKOUT_UPDATE_MISSING) == 0) + action = CHECKOUT_ACTION__CONFLICT; + else + action = CHECKOUT_ACTION__UPDATE_BLOB; + break; + + case GIT_DELTA_MODIFIED: + case GIT_DELTA_TYPECHANGE: + if (!head_entry) { + /* working dir was independently updated & does not match index */ + if ((strat & GIT_CHECKOUT_UPDATE_UNTRACKED) == 0) + action = CHECKOUT_ACTION__CONFLICT; + else + action = CHECKOUT_ACTION__UPDATE_BLOB; + } + else if (git_oid_equal(&head_entry->oid, &delta->new_file.oid)) + action = CHECKOUT_ACTION__UPDATE_BLOB; + else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) == 0) + action = CHECKOUT_ACTION__CONFLICT; + else + action = CHECKOUT_ACTION__UPDATE_BLOB; + break; + + case GIT_DELTA_UNTRACKED: + if (!head_entry) { + if ((strat & GIT_CHECKOUT_REMOVE_UNTRACKED) != 0) + action = CHECKOUT_ACTION__REMOVE; + } + else if ((strat & GIT_CHECKOUT_UPDATE_MODIFIED) != 0) { + action = CHECKOUT_ACTION__REMOVE; + } else if ((strat & GIT_CHECKOUT_UPDATE_UNMODIFIED) != 0) { + git_oid wd_oid; + + /* if HEAD matches workdir, then remove, else conflict */ + + if (git_oid_iszero(&delta->new_file.oid) && + git_diff__oid_for_file( + data->repo, delta->new_file.path, delta->new_file.mode, + delta->new_file.size, &wd_oid) < 0) + action = -1; + else if (git_oid_equal(&head_entry->oid, &wd_oid)) + action = CHECKOUT_ACTION__REMOVE; + else + action = CHECKOUT_ACTION__CONFLICT; + } else { + /* present in HEAD and workdir, but absent in index */ + action = CHECKOUT_ACTION__CONFLICT; + } + break; - if (!is_submodule && !data->create_submodules) - error = checkout_blob(data, &delta->old_file); + case GIT_DELTA_IGNORED: + default: + /* just skip these files */ + break; + } + + if (action > 0 && (action & CHECKOUT_ACTION__UPDATE_BLOB) != 0) { + if (S_ISGITLINK(delta->old_file.mode)) + action = (action & ~CHECKOUT_ACTION__UPDATE_BLOB) | + CHECKOUT_ACTION__UPDATE_SUBMODULE; - else if (is_submodule && data->create_submodules) - error = checkout_submodule(data, &delta->old_file); + action = checkout_confirm_update_blob(data, delta, action); + } + + if (action == CHECKOUT_ACTION__CONFLICT && + data->opts->conflict_cb != NULL && + data->opts->conflict_cb( + delta->old_file.path, &delta->old_file.oid, + delta->old_file.mode, delta->new_file.mode, + data->opts->conflict_payload) != 0) + { + giterr_clear(); + action = GIT_EUSER; } - if (error) - data->error = error; + if (action > 0 && (strat & GIT_CHECKOUT_UPDATE_ONLY) != 0) + action = (action & ~CHECKOUT_ACTION__REMOVE); - return error; + return action; } -static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink) +static int checkout_get_actions( + uint32_t **actions_ptr, + size_t **counts_ptr, + checkout_diff_data *data) { - git_config *cfg; int error; + git_diff_list *diff = data->diff; + git_diff_delta *delta; + size_t i, *counts = NULL; + uint32_t *actions = NULL; + git_tree *head = NULL; + git_iterator *hiter = NULL; + char *pfx = git_pathspec_prefix(&data->opts->paths); + const git_index_entry *he; + + /* if there is no HEAD, that's okay - we'll make an empty iterator */ + (void)git_repository_head_tree(&head, data->repo); + + if ((error = git_iterator_for_tree_range( + &hiter, data->repo, head, pfx, pfx)) < 0) + goto fail; + + if ((diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && + !hiter->ignore_case && + (error = git_iterator_spoolandsort( + &hiter, hiter, diff->entrycomp, true)) < 0) + goto fail; + + if ((error = git_iterator_current(hiter, &he)) < 0) + goto fail; + + git__free(pfx); + pfx = NULL; + + *counts_ptr = counts = git__calloc(CHECKOUT_ACTION__MAX+1, sizeof(size_t)); + *actions_ptr = actions = git__calloc(diff->deltas.length, sizeof(uint32_t)); + if (!counts || !actions) { + error = -1; + goto fail; + } - if (git_repository_config__weakptr(&cfg, repo) < 0) - return -1; + git_vector_foreach(&diff->deltas, i, delta) { + int cmp = -1, act; + + /* try to track HEAD entries parallel to deltas */ + while (he) { + cmp = S_ISDIR(delta->new_file.mode) ? + diff->pfxcomp(he->path, delta->new_file.path) : + diff->strcomp(he->path, delta->old_file.path); + if (cmp >= 0) + break; + if (git_iterator_advance(hiter, &he) < 0) + he = NULL; + } - error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); + act = checkout_action_for_delta(data, delta, !cmp ? he : NULL); - /* - * When no "core.symlinks" entry is found in any of the configuration - * store (local, global or system), default value is "true". - */ - if (error == GIT_ENOTFOUND) { - *can_symlink = true; - error = 0; + if (act < 0) { + error = act; + goto fail; + } + + if (!cmp && git_iterator_advance(hiter, &he) < 0) + he = NULL; + + actions[i] = act; + + if (act & CHECKOUT_ACTION__REMOVE) + counts[CHECKOUT_ACTION__REMOVE]++; + if (act & CHECKOUT_ACTION__UPDATE_BLOB) + counts[CHECKOUT_ACTION__UPDATE_BLOB]++; + if (act & CHECKOUT_ACTION__UPDATE_SUBMODULE) + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]++; + if (act & CHECKOUT_ACTION__CONFLICT) + counts[CHECKOUT_ACTION__CONFLICT]++; } - return error; + if (counts[CHECKOUT_ACTION__CONFLICT] > 0 && + (data->opts->checkout_strategy & GIT_CHECKOUT_ALLOW_CONFLICTS) == 0) + { + giterr_set(GITERR_CHECKOUT, "%d conflicts prevent checkout", + (int)counts[CHECKOUT_ACTION__CONFLICT]); + goto fail; + } + + git_iterator_free(hiter); + git_tree_free(head); + + return 0; + +fail: + *counts_ptr = NULL; + git__free(counts); + *actions_ptr = NULL; + git__free(actions); + + git_iterator_free(hiter); + git_tree_free(head); + git__free(pfx); + + return -1; } -static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed) +static int checkout_remove_the_old( + git_diff_list *diff, + unsigned int *actions, + checkout_diff_data *data) { - assert(normalized); + git_diff_delta *delta; + size_t i; - if (!proposed) - memset(normalized, 0, sizeof(git_checkout_opts)); - else - memmove(normalized, proposed, sizeof(git_checkout_opts)); + git_buf_truncate(data->path, data->workdir_len); - /* Default options */ - if (!normalized->checkout_strategy) - normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT; + git_vector_foreach(&diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__REMOVE) { + int error = git_futils_rmdir_r( + delta->new_file.path, + git_buf_cstr(data->path), /* here set to work dir root */ + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS | + GIT_RMDIR_REMOVE_BLOCKERS); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->new_file.path); + } + } - /* opts->disable_filters is false by default */ - if (!normalized->dir_mode) - normalized->dir_mode = GIT_DIR_MODE; + return 0; +} - if (!normalized->file_open_flags) - normalized->file_open_flags = O_CREAT | O_TRUNC | O_WRONLY; +static int checkout_create_the_new( + git_diff_list *diff, + unsigned int *actions, + checkout_diff_data *data) +{ + git_diff_delta *delta; + size_t i; + + git_vector_foreach(&diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_BLOB) { + int error = checkout_blob(data, &delta->old_file); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->old_file.path); + } + } + + return 0; +} + +static int checkout_create_submodules( + git_diff_list *diff, + unsigned int *actions, + checkout_diff_data *data) +{ + git_diff_delta *delta; + size_t i; + + git_vector_foreach(&diff->deltas, i, delta) { + if (actions[i] & CHECKOUT_ACTION__UPDATE_SUBMODULE) { + int error = checkout_submodule(data, &delta->old_file); + if (error < 0) + return error; + + data->completed_steps++; + report_progress(data, delta->old_file.path); + } + } + + return 0; } int git_checkout_index( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats) + git_index *index, + git_checkout_opts *opts) { git_diff_list *diff = NULL; - git_indexer_stats dummy_stats; - git_diff_options diff_opts = {0}; git_checkout_opts checkout_opts; - - struct checkout_diff_data data; + checkout_diff_data data; git_buf workdir = GIT_BUF_INIT; - + uint32_t *actions = NULL; + size_t *counts = NULL; int error; assert(repo); @@ -324,14 +619,13 @@ int git_checkout_index( return error; diff_opts.flags = - GIT_DIFF_INCLUDE_UNTRACKED | - GIT_DIFF_INCLUDE_TYPECHANGE | - GIT_DIFF_SKIP_BINARY_CHECK; + GIT_DIFF_INCLUDE_UNMODIFIED | GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_INCLUDE_TYPECHANGE | GIT_DIFF_SKIP_BINARY_CHECK; if (opts && opts->paths.count > 0) diff_opts.pathspec = opts->paths; - if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) + if ((error = git_diff_workdir_to_index(&diff, repo, index, &diff_opts)) < 0) goto cleanup; if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0) @@ -339,27 +633,10 @@ int git_checkout_index( normalize_options(&checkout_opts, opts); - if (!stats) - stats = &dummy_stats; - - stats->processed = 0; - /* total based on 3 passes, but it might be 2 if no submodules */ - stats->total = (unsigned int)git_diff_num_deltas(diff) * 3; - - memset(&data, 0, sizeof(data)); - - data.path = &workdir; - data.workdir_len = git_buf_len(&workdir); - data.checkout_opts = &checkout_opts; - data.stats = stats; - data.owner = repo; - - if ((error = retrieve_symlink_capabilities(repo, &data.can_symlink)) < 0) - goto cleanup; - - /* Checkout is best performed with three passes through the diff. + /* Checkout is best performed with up to four passes through the diff. * - * 1. First do removes, because we iterate in alphabetical order, thus + * 0. Figure out what actions should be taken and record for later. + * 1. Next do removes, because we iterate in alphabetical order, thus * a new untracked directory will end up sorted *after* a blob that * should be checked out with the same name. * 2. Then checkout all blobs. @@ -367,23 +644,45 @@ int git_checkout_index( * checked out during pass #2. */ - if (!(error = git_diff_foreach( - diff, &data, checkout_remove_the_old, NULL, NULL)) && - !(error = git_diff_foreach( - diff, &data, checkout_create_the_new, NULL, NULL)) && - data.found_submodules) - { - data.create_submodules = true; - error = git_diff_foreach( - diff, &data, checkout_create_the_new, NULL, NULL); - } + memset(&data, 0, sizeof(data)); + data.path = &workdir; + data.workdir_len = git_buf_len(&workdir); + data.repo = repo; + data.diff = diff; + data.opts = &checkout_opts; + + if ((error = checkout_get_actions(&actions, &counts, &data)) < 0) + goto cleanup; + + data.total_steps = counts[CHECKOUT_ACTION__REMOVE] + + counts[CHECKOUT_ACTION__UPDATE_BLOB] + + counts[CHECKOUT_ACTION__UPDATE_SUBMODULE]; + + if ((error = retrieve_symlink_caps(repo, &data.can_symlink)) < 0) + goto cleanup; + + report_progress(&data, NULL); /* establish 0 baseline */ + + if (counts[CHECKOUT_ACTION__REMOVE] > 0 && + (error = checkout_remove_the_old(diff, actions, &data)) < 0) + goto cleanup; - stats->processed = stats->total; + if (counts[CHECKOUT_ACTION__UPDATE_BLOB] > 0 && + (error = checkout_create_the_new(diff, actions, &data)) < 0) + goto cleanup; + + if (counts[CHECKOUT_ACTION__UPDATE_SUBMODULE] > 0 && + (error = checkout_create_submodules(diff, actions, &data)) < 0) + goto cleanup; + + assert(data.completed_steps == data.total_steps); cleanup: if (error == GIT_EUSER) - error = (data.error != 0) ? data.error : -1; + giterr_clear(); + git__free(actions); + git__free(counts); git_diff_list_free(diff); git_buf_free(&workdir); @@ -393,58 +692,49 @@ cleanup: int git_checkout_tree( git_repository *repo, git_object *treeish, - git_checkout_opts *opts, - git_indexer_stats *stats) + git_checkout_opts *opts) { + int error = 0; git_index *index = NULL; git_tree *tree = NULL; - int error; - assert(repo && treeish); if (git_object_peel((git_object **)&tree, treeish, GIT_OBJ_TREE) < 0) { - giterr_set(GITERR_INVALID, "Provided treeish cannot be peeled into a tree."); - return GIT_ERROR; + giterr_set( + GITERR_CHECKOUT, "Provided object cannot be peeled to a tree"); + return -1; } - if ((error = git_repository_index(&index, repo)) < 0) - goto cleanup; + /* TODO: create a temp index, load tree there and check it out */ - if ((error = git_index_read_tree(index, tree, NULL)) < 0) - goto cleanup; + /* load paths in tree that match pathspec into index */ + if (!(error = git_repository_index(&index, repo)) && + !(error = git_index_read_tree_match( + index, tree, opts ? &opts->paths : NULL)) && + !(error = git_index_write(index))) + error = git_checkout_index(repo, NULL, opts); - if ((error = git_index_write(index)) < 0) - goto cleanup; - - error = git_checkout_index(repo, opts, stats); - -cleanup: git_index_free(index); git_tree_free(tree); + return error; } int git_checkout_head( git_repository *repo, - git_checkout_opts *opts, - git_indexer_stats *stats) + git_checkout_opts *opts) { - git_reference *head; int error; + git_reference *head = NULL; git_object *tree = NULL; assert(repo); - if ((error = git_repository_head(&head, repo)) < 0) - return error; - - if ((error = git_reference_peel(&tree, head, GIT_OBJ_TREE)) < 0) - goto cleanup; + if (!(error = git_repository_head(&head, repo)) && + !(error = git_reference_peel(&tree, head, GIT_OBJ_TREE))) + error = git_checkout_tree(repo, tree, opts); - error = git_checkout_tree(repo, tree, opts, stats); - -cleanup: git_reference_free(head); git_object_free(tree); diff --git a/src/clone.c b/src/clone.c index 85e69ad97..9ef6f8100 100644 --- a/src/clone.c +++ b/src/clone.c @@ -18,7 +18,6 @@ #include "common.h" #include "remote.h" -#include "pkt.h" #include "fileops.h" #include "refs.h" #include "path.h" @@ -171,11 +170,19 @@ static int update_head_to_new_branch( return error; } +static int get_head_callback(git_remote_head *head, void *payload) +{ + git_remote_head **destination = (git_remote_head **)payload; + + /* Save the first entry, and terminate the enumeration */ + *destination = head; + return 1; +} + static int update_head_to_remote(git_repository *repo, git_remote *remote) { int retcode = -1; git_remote_head *remote_head; - git_pkt_ref *pkt; struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; @@ -189,8 +196,13 @@ static int update_head_to_remote(git_repository *repo, git_remote *remote) } /* Get the remote's HEAD. This is always the first ref in remote->refs. */ - pkt = remote->transport->refs.contents[0]; - remote_head = &pkt->head; + remote_head = NULL; + + if (!remote->transport->ls(remote->transport, get_head_callback, &remote_head)) + return -1; + + assert(remote_head); + git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); git_buf_init(&head_info.branchname, 16); head_info.repo = repo; @@ -248,22 +260,26 @@ cleanup: -static int setup_remotes_and_fetch(git_repository *repo, - const char *origin_url, - git_indexer_stats *fetch_stats) +static int setup_remotes_and_fetch( + git_repository *repo, + const char *origin_url, + git_transfer_progress_callback progress_cb, + void *progress_payload) { int retcode = GIT_ERROR; git_remote *origin = NULL; - git_off_t bytes = 0; - git_indexer_stats dummy_stats; - - if (!fetch_stats) fetch_stats = &dummy_stats; /* Create the "origin" remote */ if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) { + /* + * Don't write FETCH_HEAD, we'll check out the remote tracking + * branch ourselves based on the server's default. + */ + git_remote_set_update_fetchhead(origin, 0); + /* Connect and download everything */ if (!git_remote_connect(origin, GIT_DIR_FETCH)) { - if (!git_remote_download(origin, &bytes, fetch_stats)) { + if (!git_remote_download(origin, progress_cb, progress_payload)) { /* Create "origin/foo" branches for all remote branches */ if (!git_remote_update_tips(origin)) { /* Point HEAD to the same ref as the remote's head */ @@ -311,26 +327,24 @@ static int clone_internal( git_repository **out, const char *origin_url, const char *path, - git_indexer_stats *fetch_stats, - git_indexer_stats *checkout_stats, + git_transfer_progress_callback fetch_progress_cb, + void *fetch_progress_payload, git_checkout_opts *checkout_opts, bool is_bare) { int retcode = GIT_ERROR; git_repository *repo = NULL; - git_indexer_stats dummy_stats; - - if (!fetch_stats) fetch_stats = &dummy_stats; if (!path_is_okay(path)) { return GIT_ERROR; } if (!(retcode = git_repository_init(&repo, path, is_bare))) { - if ((retcode = setup_remotes_and_fetch(repo, origin_url, fetch_stats)) < 0) { + if ((retcode = setup_remotes_and_fetch(repo, origin_url, + fetch_progress_cb, fetch_progress_payload)) < 0) { /* Failed to fetch; clean up */ git_repository_free(repo); - git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS); + git_futils_rmdir_r(path, NULL, GIT_RMDIR_REMOVE_FILES); } else { *out = repo; retcode = 0; @@ -338,15 +352,17 @@ static int clone_internal( } if (!retcode && should_checkout(repo, is_bare, checkout_opts)) - retcode = git_checkout_head(*out, checkout_opts, checkout_stats); + retcode = git_checkout_head(*out, checkout_opts); return retcode; } -int git_clone_bare(git_repository **out, - const char *origin_url, - const char *dest_path, - git_indexer_stats *fetch_stats) +int git_clone_bare( + git_repository **out, + const char *origin_url, + const char *dest_path, + git_transfer_progress_callback fetch_progress_cb, + void *fetch_progress_payload) { assert(out && origin_url && dest_path); @@ -354,19 +370,20 @@ int git_clone_bare(git_repository **out, out, origin_url, dest_path, - fetch_stats, - NULL, + fetch_progress_cb, + fetch_progress_payload, NULL, 1); } -int git_clone(git_repository **out, - const char *origin_url, - const char *workdir_path, - git_indexer_stats *fetch_stats, - git_indexer_stats *checkout_stats, - git_checkout_opts *checkout_opts) +int git_clone( + git_repository **out, + const char *origin_url, + const char *workdir_path, + git_transfer_progress_callback fetch_progress_cb, + void *fetch_progress_payload, + git_checkout_opts *checkout_opts) { assert(out && origin_url && workdir_path); @@ -374,8 +391,8 @@ int git_clone(git_repository **out, out, origin_url, workdir_path, - fetch_stats, - checkout_stats, + fetch_progress_cb, + fetch_progress_payload, checkout_opts, 0); } diff --git a/src/common.h b/src/common.h index 747bbf7ce..a35239e3d 100644 --- a/src/common.h +++ b/src/common.h @@ -69,7 +69,4 @@ void giterr_set_regex(const regex_t *regex, int error_code); #include "util.h" -typedef struct git_transport git_transport; -typedef struct gitno_buffer gitno_buffer; - #endif /* INCLUDE_common_h__ */ diff --git a/src/config.c b/src/config.c index f9bd205af..fc9ea65fe 100644 --- a/src/config.c +++ b/src/config.c @@ -90,6 +90,13 @@ int git_config_add_file_ondisk( git_config_file *file = NULL; int res; + assert(cfg && path); + + if (!git_path_isfile(path)) { + giterr_set(GITERR_CONFIG, "Cannot find config file '%s'", path); + return GIT_ENOTFOUND; + } + if (git_config_file__ondisk(&file, path) < 0) return -1; @@ -105,17 +112,22 @@ int git_config_add_file_ondisk( return 0; } -int git_config_open_ondisk(git_config **cfg, const char *path) +int git_config_open_ondisk(git_config **out, const char *path) { - if (git_config_new(cfg) < 0) - return -1; + int error; + git_config *config; + + *out = NULL; - if (git_config_add_file_ondisk(*cfg, path, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) { - git_config_free(*cfg); + if (git_config_new(&config) < 0) return -1; - } - return 0; + if ((error = git_config_add_file_ondisk(config, path, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0) + git_config_free(config); + else + *out = config; + + return error; } static int find_internal_file_by_level( @@ -127,8 +139,6 @@ static int find_internal_file_by_level( file_internal *internal; unsigned int i; - assert(cfg->files.length); - /* when passing GIT_CONFIG_HIGHEST_LEVEL, the idea is to get the config file * which has the highest level. As config files are stored in a vector * sorted by decreasing order of level, getting the file at position 0 @@ -267,6 +277,20 @@ int git_config_add_file( return 0; } +int git_config_refresh(git_config *cfg) +{ + int error = 0; + unsigned int i; + + for (i = 0; i < cfg->files.length && !error; ++i) { + file_internal *internal = git_vector_get(&cfg->files, i); + git_config_file *file = internal->file; + error = file->refresh(file); + } + + return error; +} + /* * Loop over all the variables */ @@ -381,32 +405,19 @@ int git_config_get_int32(int32_t *out, git_config *cfg, const char *name) return git_config_parse_int32(out, value); } -int git_config_get_bool(int *out, git_config *cfg, const char *name) -{ - const char *value; - int ret; - - if ((ret = git_config_get_string(&value, cfg, name)) < 0) - return ret; - - return git_config_parse_bool(out, value); -} - static int get_string_at_file(const char **out, git_config_file *file, const char *name) { const git_config_entry *entry; int res; - *out = NULL; - res = file->get(file, name, &entry); - if (res != GIT_ENOTFOUND) + if (!res) *out = entry->value; return res; } -int git_config_get_string(const char **out, git_config *cfg, const char *name) +static int get_string(const char **out, git_config *cfg, const char *name) { file_internal *internal; unsigned int i; @@ -423,7 +434,30 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name) return GIT_ENOTFOUND; } -int git_config_get_config_entry(const git_config_entry **out, git_config *cfg, const char *name) +int git_config_get_bool(int *out, git_config *cfg, const char *name) +{ + const char *value = NULL; + int ret; + + if ((ret = get_string(&value, cfg, name)) < 0) + return ret; + + return git_config_parse_bool(out, value); +} + +int git_config_get_string(const char **out, git_config *cfg, const char *name) +{ + int ret; + const char *str = NULL; + + if ((ret = get_string(&str, cfg, name)) < 0) + return ret; + + *out = str == NULL ? "" : str; + return 0; +} + +int git_config_get_entry(const git_config_entry **out, git_config *cfg, const char *name) { file_internal *internal; unsigned int i; @@ -720,3 +754,81 @@ fail_parse: giterr_set(GITERR_CONFIG, "Failed to parse '%s' as a 32-bit integer", value); return -1; } + +struct rename_data +{ + git_config *config; + const char *old_name; + const char *new_name; +}; + +static int rename_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + struct rename_data *data = (struct rename_data *)payload; + + if (data->new_name != NULL) { + git_buf name = GIT_BUF_INIT; + int error; + + if (git_buf_printf( + &name, + "%s.%s", + data->new_name, + entry->name + strlen(data->old_name) + 1) < 0) + return -1; + + error = git_config_set_string( + data->config, + git_buf_cstr(&name), + entry->value); + + git_buf_free(&name); + + if (error) + return error; + } + + return git_config_delete(data->config, entry->name); +} + +int git_config_rename_section( + git_repository *repo, + const char *old_section_name, + const char *new_section_name) +{ + git_config *config; + git_buf pattern = GIT_BUF_INIT; + int error = -1; + struct rename_data data; + + git_buf_puts_escape_regex(&pattern, old_section_name); + git_buf_puts(&pattern, "\\..+"); + if (git_buf_oom(&pattern)) + goto cleanup; + + if (git_repository_config__weakptr(&config, repo) < 0) + goto cleanup; + + data.config = config; + data.old_name = old_section_name; + data.new_name = new_section_name; + + if ((error = git_config_foreach_match( + config, + git_buf_cstr(&pattern), + rename_config_entries_cb, &data)) < 0) { + giterr_set(GITERR_CONFIG, + "Cannot rename config section '%s' to '%s'", + old_section_name, + new_section_name); + goto cleanup; + } + + error = 0; + +cleanup: + git_buf_free(&pattern); + return error; +} diff --git a/src/config.h b/src/config.h index 16b8413b7..c7595ee79 100644 --- a/src/config.h +++ b/src/config.h @@ -27,4 +27,22 @@ extern int git_config_find_global_r(git_buf *global_config_path); extern int git_config_find_xdg_r(git_buf *system_config_path); extern int git_config_find_system_r(git_buf *system_config_path); +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 */ + +/** + * 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_file__ondisk(struct git_config_file **out, const char *path); + #endif diff --git a/src/config_file.c b/src/config_file.c index 92fe13296..4d9f99986 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -75,7 +75,11 @@ typedef struct { int eof; } reader; - char *file_path; + char *file_path; + time_t file_mtime; + size_t file_size; + + unsigned int level; } diskfile_backend; static int config_parse(diskfile_backend *cfg_file, unsigned int level); @@ -150,25 +154,53 @@ static int config_open(git_config_file *cfg, unsigned int level) int res; diskfile_backend *b = (diskfile_backend *)cfg; + b->level = level; + b->values = git_strmap_alloc(); GITERR_CHECK_ALLOC(b->values); git_buf_init(&b->reader.buffer, 0); - res = git_futils_readbuffer(&b->reader.buffer, b->file_path); + res = git_futils_readbuffer_updated( + &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, NULL); /* It's fine if the file doesn't exist */ if (res == GIT_ENOTFOUND) return 0; - if (res < 0 || config_parse(b, level) < 0) { + if (res < 0 || (res = config_parse(b, level)) < 0) { free_vars(b->values); b->values = NULL; - git_buf_free(&b->reader.buffer); - return -1; } git_buf_free(&b->reader.buffer); - return 0; + return res; +} + +static int config_refresh(git_config_file *cfg) +{ + int res, updated = 0; + diskfile_backend *b = (diskfile_backend *)cfg; + git_strmap *old_values; + + res = git_futils_readbuffer_updated( + &b->reader.buffer, b->file_path, &b->file_mtime, &b->file_size, &updated); + if (res < 0 || !updated) + return (res == GIT_ENOTFOUND) ? 0 : res; + + /* need to reload - store old values and prep for reload */ + old_values = b->values; + b->values = git_strmap_alloc(); + GITERR_CHECK_ALLOC(b->values); + + if ((res = config_parse(b, b->level)) < 0) { + free_vars(b->values); + b->values = old_values; + } else { + free_vars(old_values); + } + + git_buf_free(&b->reader.buffer); + return res; } static void backend_free(git_config_file *_backend) @@ -527,6 +559,7 @@ int git_config_file__ondisk(git_config_file **out, const char *path) backend->parent.set_multivar = config_set_multivar; backend->parent.del = config_delete; backend->parent.foreach = file_foreach; + backend->parent.refresh = config_refresh; backend->parent.free = backend_free; *out = (git_config_file *)backend; @@ -891,7 +924,7 @@ static int strip_comments(char *line, int in_quotes) } /* skip any space at the end */ - if (git__isspace(ptr[-1])) { + if (ptr > line && git__isspace(ptr[-1])) { ptr--; } ptr[0] = '\0'; @@ -1197,8 +1230,12 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p git__free(section); git__free(current_section); + /* refresh stats - if this errors, then commit will error too */ + (void)git_filebuf_stats(&cfg->file_mtime, &cfg->file_size, &file); + result = git_filebuf_commit(&file, GIT_CONFIG_FILE_MODE); git_buf_free(&cfg->reader.buffer); + return result; rewrite_fail: @@ -1361,7 +1398,7 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val value_start = var_end + 1; do var_end--; - while (git__isspace(*var_end)); + while (var_end>line && git__isspace(*var_end)); *var_name = git__strndup(line, var_end - line + 1); GITERR_CHECK_ALLOC(*var_name); @@ -1395,8 +1432,10 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val else if (value_start[0] != '\0') { *var_value = fixup_line(value_start, 0); GITERR_CHECK_ALLOC(*var_value); + } else { /* equals sign but missing rhs */ + *var_value = git__strdup(""); + GITERR_CHECK_ALLOC(*var_value); } - } git__free(line); diff --git a/src/diff.c b/src/diff.c index 9f693bebf..d6f5bd454 100644 --- a/src/diff.c +++ b/src/diff.c @@ -10,76 +10,7 @@ #include "config.h" #include "attr_file.h" #include "filter.h" - -static char *diff_prefix_from_pathspec(const git_strarray *pathspec) -{ - git_buf prefix = GIT_BUF_INIT; - const char *scan; - - if (git_buf_common_prefix(&prefix, pathspec) < 0) - return NULL; - - /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan; ++scan) { - if (git__iswildcard(*scan) && - (scan == prefix.ptr || (*(scan - 1) != '\\'))) - break; - } - git_buf_truncate(&prefix, scan - prefix.ptr); - - if (prefix.size <= 0) { - git_buf_free(&prefix); - return NULL; - } - - git_buf_unescape(&prefix); - - return git_buf_detach(&prefix); -} - -static bool diff_pathspec_is_interesting(const git_strarray *pathspec) -{ - const char *str; - - if (pathspec == NULL || pathspec->count == 0) - return false; - if (pathspec->count > 1) - return true; - - str = pathspec->strings[0]; - if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) - return false; - return true; -} - -static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) -{ - unsigned int i; - git_attr_fnmatch *match; - - if (!diff->pathspec.length) - return true; - - git_vector_foreach(&diff->pathspec, i, match) { - int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0; - - if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) && - result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, 0); - - /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - strncmp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; - - if (result == 0) - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; - } - - return false; -} +#include "pathspec.h" static git_diff_delta *diff_delta__alloc( git_diff_list *diff, @@ -110,85 +41,6 @@ static git_diff_delta *diff_delta__alloc( return delta; } -static git_diff_delta *diff_delta__dup( - const git_diff_delta *d, git_pool *pool) -{ - git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); - if (!delta) - return NULL; - - memcpy(delta, d, sizeof(git_diff_delta)); - - delta->old_file.path = git_pool_strdup(pool, d->old_file.path); - if (delta->old_file.path == NULL) - goto fail; - - if (d->new_file.path != d->old_file.path) { - delta->new_file.path = git_pool_strdup(pool, d->new_file.path); - if (delta->new_file.path == NULL) - goto fail; - } else { - delta->new_file.path = delta->old_file.path; - } - - return delta; - -fail: - git__free(delta); - return NULL; -} - -static git_diff_delta *diff_delta__merge_like_cgit( - const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) -{ - git_diff_delta *dup; - - /* Emulate C git for merging two diffs (a la 'git diff <sha>'). - * - * When C git does a diff between the work dir and a tree, it actually - * diffs with the index but uses the workdir contents. This emulates - * those choices so we can emulate the type of diff. - * - * We have three file descriptions here, let's call them: - * f1 = a->old_file - * f2 = a->new_file AND b->old_file - * f3 = b->new_file - */ - - /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ - if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) - return diff_delta__dup(a, pool); - - /* otherwise, base this diff on the 'b' diff */ - if ((dup = diff_delta__dup(b, pool)) == NULL) - return NULL; - - /* If 'a' status is uninteresting, then we're done */ - if (a->status == GIT_DELTA_UNMODIFIED) - return dup; - - assert(a->status != GIT_DELTA_UNMODIFIED); - assert(b->status != GIT_DELTA_UNMODIFIED); - - /* A cgit exception is that the diff of a file that is only in the - * index (i.e. not in HEAD nor workdir) is given as empty. - */ - if (dup->status == GIT_DELTA_DELETED) { - if (a->status == GIT_DELTA_ADDED) - dup->status = GIT_DELTA_UNMODIFIED; - /* else don't overwrite DELETE status */ - } else { - dup->status = a->status; - } - - git_oid_cpy(&dup->old_file.oid, &a->old_file.oid); - dup->old_file.mode = a->old_file.mode; - dup->old_file.size = a->old_file.size; - dup->old_file.flags = a->old_file.flags; - - return dup; -} - static int diff_delta__from_one( git_diff_list *diff, git_delta_t status, @@ -204,7 +56,10 @@ static int diff_delta__from_one( (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) return 0; - if (!diff_path_matches_pathspec(diff, entry->path)) + if (!git_pathspec_match_path( + &diff->pathspec, entry->path, + (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, + (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -332,13 +187,34 @@ static char *diff_strdup_prefix(git_pool *pool, const char *prefix) return git_pool_strndup(pool, prefix, len + 1); } -static int diff_delta__cmp(const void *a, const void *b) +int git_diff_delta__cmp(const void *a, const void *b) { const git_diff_delta *da = a, *db = b; int val = strcmp(da->old_file.path, db->old_file.path); return val ? val : ((int)da->status - (int)db->status); } +bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta) +{ + uint32_t flags = opts ? opts->flags : 0; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; + + if (delta->status == GIT_DELTA_IGNORED && + (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNTRACKED && + (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; + + return false; +} + + static int config_bool(git_config *cfg, const char *name, int defvalue) { int val = defvalue; @@ -353,7 +229,6 @@ static git_diff_list *git_diff_list_alloc( git_repository *repo, const git_diff_options *opts) { git_config *cfg; - size_t i; git_diff_list *diff = git__calloc(1, sizeof(git_diff_list)); if (diff == NULL) return NULL; @@ -361,7 +236,7 @@ static git_diff_list *git_diff_list_alloc( GIT_REFCOUNT_INC(diff); diff->repo = repo; - if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || + if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0 || git_pool_init(&diff->pool, 1, 0) < 0) goto fail; @@ -378,11 +253,28 @@ static git_diff_list *git_diff_list_alloc( diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ + /* TODO: there are certain config settings where even if we were + * not given an options structure, we need the diff list to have one + * so that we can store the altered default values. + * + * - diff.ignoreSubmodules + * - diff.mnemonicprefix + * - diff.noprefix + */ + if (opts == NULL) return diff; memcpy(&diff->opts, opts, sizeof(git_diff_options)); - memset(&diff->opts.pathspec, 0, sizeof(diff->opts.pathspec)); + + if(opts->flags & GIT_DIFF_IGNORE_FILEMODE) + diff->diffcaps = diff->diffcaps & ~GIT_DIFFCAPS_TRUST_MODE_BITS; + + /* pathspec init will do nothing for empty pathspec */ + if (git_pathspec_init(&diff->pathspec, &opts->pathspec, &diff->pool) < 0) + goto fail; + + /* TODO: handle config diff.mnemonicprefix, diff.noprefix */ diff->opts.old_prefix = diff_strdup_prefix(&diff->pool, opts->old_prefix ? opts->old_prefix : DIFF_OLD_PREFIX_DEFAULT); @@ -402,35 +294,6 @@ static git_diff_list *git_diff_list_alloc( if (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - /* only copy pathspec if it is "interesting" so we can test - * diff->pathspec.length > 0 to know if it is worth calling - * fnmatch as we iterate. - */ - if (!diff_pathspec_is_interesting(&opts->pathspec)) - return diff; - - if (git_vector_init( - &diff->pathspec, (unsigned int)opts->pathspec.count, NULL) < 0) - goto fail; - - for (i = 0; i < opts->pathspec.count; ++i) { - int ret; - const char *pattern = opts->pathspec.strings[i]; - git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); - if (!match) - goto fail; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; - ret = git_attr_fnmatch__parse(match, &diff->pool, NULL, &pattern); - if (ret == GIT_ENOTFOUND) { - git__free(match); - continue; - } else if (ret < 0) - goto fail; - - if (git_vector_insert(&diff->pathspec, match) < 0) - goto fail; - } - return diff; fail: @@ -441,7 +304,6 @@ fail: static void diff_list_free(git_diff_list *diff) { git_diff_delta *delta; - git_attr_fnmatch *match; unsigned int i; git_vector_foreach(&diff->deltas, i, delta) { @@ -450,12 +312,7 @@ static void diff_list_free(git_diff_list *diff) } git_vector_free(&diff->deltas); - git_vector_foreach(&diff->pathspec, i, match) { - git__free(match); - diff->pathspec.contents[i] = NULL; - } - git_vector_free(&diff->pathspec); - + git_pathspec_free(&diff->pathspec); git_pool_clear(&diff->pool); git__free(diff); } @@ -473,24 +330,39 @@ void git_diff_list_addref(git_diff_list *diff) GIT_REFCOUNT_INC(diff); } -static int oid_for_workdir_item( +int git_diff__oid_for_file( git_repository *repo, - const git_index_entry *item, + const char *path, + uint16_t mode, + git_off_t size, git_oid *oid) { int result = 0; git_buf full_path = GIT_BUF_INIT; if (git_buf_joinpath( - &full_path, git_repository_workdir(repo), item->path) < 0) + &full_path, git_repository_workdir(repo), path) < 0) return -1; + if (!mode) { + struct stat st; + + if (p_stat(path, &st) < 0) { + giterr_set(GITERR_OS, "Could not stat '%s'", path); + result = -1; + goto cleanup; + } + + mode = st.st_mode; + size = st.st_size; + } + /* calculate OID for file if possible */ - if (S_ISGITLINK(item->mode)) { + if (S_ISGITLINK(mode)) { git_submodule *sm; const git_oid *sm_oid; - if (!git_submodule_lookup(&sm, repo, item->path) && + if (!git_submodule_lookup(&sm, repo, path) && (sm_oid = git_submodule_wd_oid(sm)) != NULL) git_oid_cpy(oid, sm_oid); else { @@ -500,23 +372,22 @@ static int oid_for_workdir_item( giterr_clear(); memset(oid, 0, sizeof(*oid)); } - } else if (S_ISLNK(item->mode)) + } else if (S_ISLNK(mode)) { result = git_odb__hashlink(oid, full_path.ptr); - else if (!git__is_sizet(item->file_size)) { - giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + } else if (!git__is_sizet(size)) { + giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", path); result = -1; } else { git_vector filters = GIT_VECTOR_INIT; - result = git_filters_load( - &filters, repo, item->path, GIT_FILTER_TO_ODB); + result = git_filters_load(&filters, repo, path, GIT_FILTER_TO_ODB); if (result >= 0) { int fd = git_futils_open_ro(full_path.ptr); if (fd < 0) result = fd; else { result = git_odb__hashfd_filtered( - oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB, &filters); + oid, fd, (size_t)size, GIT_OBJ_BLOB, &filters); p_close(fd); } } @@ -524,8 +395,8 @@ static int oid_for_workdir_item( git_filters_free(&filters); } +cleanup: git_buf_free(&full_path); - return result; } @@ -546,7 +417,10 @@ static int maybe_modified( GIT_UNUSED(old_iter); - if (!diff_path_matches_pathspec(diff, oitem->path)) + if (!git_pathspec_match_path( + &diff->pathspec, oitem->path, + (diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) != 0, + (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0)) return 0; /* on platforms with no symlinks, preserve mode of existing symlinks */ @@ -582,8 +456,7 @@ static int maybe_modified( } /* if oids and modes match, then file is unmodified */ - else if (git_oid_cmp(&oitem->oid, &nitem->oid) == 0 && - omode == nmode) + else if (git_oid_equal(&oitem->oid, &nitem->oid) && omode == nmode) status = GIT_DELTA_UNMODIFIED; /* if we have an unknown OID and a workdir iterator, then check some @@ -637,44 +510,28 @@ static int maybe_modified( * haven't calculated the OID of the new item, then calculate it now */ if (status != GIT_DELTA_UNMODIFIED && git_oid_iszero(&nitem->oid)) { - if (oid_for_workdir_item(diff->repo, nitem, &noid) < 0) - return -1; - else if (omode == nmode && git_oid_equal(&oitem->oid, &noid)) + if (!use_noid) { + if (git_diff__oid_for_file(diff->repo, + nitem->path, nitem->mode, nitem->file_size, &noid) < 0) + return -1; + use_noid = &noid; + } + if (omode == nmode && git_oid_equal(&oitem->oid, use_noid)) status = GIT_DELTA_UNMODIFIED; - - /* store calculated oid so we don't have to recalc later */ - use_noid = &noid; } return diff_delta__from_two( diff, status, oitem, omode, nitem, nmode, use_noid); } -static int git_index_entry_cmp_case(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); -} - -static int git_index_entry_cmp_icase(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); -} - static bool entry_is_prefixed( + git_diff_list *diff, const git_index_entry *item, - git_iterator *prefix_iterator, const git_index_entry *prefix_item) { size_t pathlen; - if (!prefix_item || - ITERATOR_PREFIXCMP(*prefix_iterator, prefix_item->path, item->path)) + if (!prefix_item || diff->pfxcomp(prefix_item->path, item->path)) return false; pathlen = strlen(item->path); @@ -684,44 +541,67 @@ static bool entry_is_prefixed( prefix_item->path[pathlen] == '/'); } -static int diff_from_iterators( - git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ +static int diff_list_init_from_iterators( + git_diff_list *diff, git_iterator *old_iter, - git_iterator *new_iter, - git_diff_list **diff_ptr) + git_iterator *new_iter) { - const git_index_entry *oitem, *nitem; - git_buf ignore_prefix = GIT_BUF_INIT; - git_diff_list *diff = git_diff_list_alloc(repo, opts); - git_vector_cmp entry_compare; - - if (!diff) - goto fail; - diff->old_src = old_iter->type; diff->new_src = new_iter->type; /* Use case-insensitive compare if either iterator has * the ignore_case bit set */ if (!old_iter->ignore_case && !new_iter->ignore_case) { - entry_compare = git_index_entry_cmp_case; diff->opts.flags &= ~GIT_DIFF_DELTAS_ARE_ICASE; + + diff->strcomp = git__strcmp; + diff->strncomp = git__strncmp; + diff->pfxcomp = git__prefixcmp; + diff->entrycomp = git_index_entry__cmp; } else { - entry_compare = git_index_entry_cmp_icase; diff->opts.flags |= GIT_DIFF_DELTAS_ARE_ICASE; + diff->strcomp = git__strcasecmp; + diff->strncomp = git__strncasecmp; + diff->pfxcomp = git__prefixcmp_icase; + diff->entrycomp = git_index_entry__cmp_icase; + } + + return 0; +} + +static int diff_from_iterators( + git_diff_list **diff_ptr, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts) +{ + int error = 0; + const git_index_entry *oitem, *nitem; + git_buf ignore_prefix = GIT_BUF_INIT; + git_diff_list *diff = git_diff_list_alloc(repo, opts); + + *diff_ptr = NULL; + + if (!diff || + diff_list_init_from_iterators(diff, old_iter, new_iter) < 0) + goto fail; + + if (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) { /* If one of the iterators doesn't have ignore_case set, * then that's unfortunate because we'll have to spool * its data, sort it icase, and then use that for our * merge join to the other iterator that is icase sorted */ - if (!old_iter->ignore_case) { - if (git_iterator_spoolandsort(&old_iter, old_iter, git_index_entry_cmp_icase, true) < 0) - goto fail; - } else if (!new_iter->ignore_case) { - if (git_iterator_spoolandsort(&new_iter, new_iter, git_index_entry_cmp_icase, true) < 0) - goto fail; - } + if (!old_iter->ignore_case && + git_iterator_spoolandsort( + &old_iter, old_iter, diff->entrycomp, true) < 0) + goto fail; + + if (!new_iter->ignore_case && + git_iterator_spoolandsort( + &new_iter, new_iter, diff->entrycomp, true) < 0) + goto fail; } if (git_iterator_current(old_iter, &oitem) < 0 || @@ -732,7 +612,7 @@ static int diff_from_iterators( while (oitem || nitem) { /* create DELETED records for old items not matched in new */ - if (oitem && (!nitem || entry_compare(oitem, nitem) < 0)) { + if (oitem && (!nitem || diff->entrycomp(oitem, nitem) < 0)) { if (diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem) < 0) goto fail; @@ -740,7 +620,7 @@ static int diff_from_iterators( * instead of just generating a DELETE record */ if ((diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && - entry_is_prefixed(oitem, new_iter, nitem)) + entry_is_prefixed(diff, oitem, nitem)) { /* this entry has become a tree! convert to TYPECHANGE */ git_diff_delta *last = diff_delta__last_for_item(diff, oitem); @@ -757,13 +637,12 @@ static int diff_from_iterators( /* create ADDED, TRACKED, or IGNORED records for new items not * matched in old (and/or descend into directories as needed) */ - else if (nitem && (!oitem || entry_compare(oitem, nitem) > 0)) { + else if (nitem && (!oitem || diff->entrycomp(oitem, nitem) > 0)) { git_delta_t delta_type = GIT_DELTA_UNTRACKED; /* check if contained in ignored parent directory */ if (git_buf_len(&ignore_prefix) && - ITERATOR_PREFIXCMP(*old_iter, nitem->path, - git_buf_cstr(&ignore_prefix)) == 0) + diff->pfxcomp(nitem->path, git_buf_cstr(&ignore_prefix)) == 0) delta_type = GIT_DELTA_IGNORED; if (S_ISDIR(nitem->mode)) { @@ -772,7 +651,7 @@ static int diff_from_iterators( * directories and it is not under an ignored directory. */ bool contains_tracked = - entry_is_prefixed(nitem, old_iter, oitem); + entry_is_prefixed(diff, nitem, oitem); bool recurse_untracked = (delta_type == GIT_DELTA_UNTRACKED && (diff->opts.flags & GIT_DIFF_RECURSE_UNTRACKED_DIRS) != 0); @@ -836,7 +715,7 @@ static int diff_from_iterators( */ if (delta_type != GIT_DELTA_IGNORED && (diff->opts.flags & GIT_DIFF_INCLUDE_TYPECHANGE_TREES) != 0 && - entry_is_prefixed(nitem, old_iter, oitem)) + entry_is_prefixed(diff, nitem, oitem)) { /* this entry was a tree! convert to TYPECHANGE */ git_diff_delta *last = diff_delta__last_for_item(diff, oitem); @@ -854,7 +733,7 @@ static int diff_from_iterators( * (or ADDED and DELETED pair if type changed) */ else { - assert(oitem && nitem && entry_compare(oitem, nitem) == 0); + assert(oitem && nitem && diff->entrycomp(oitem, nitem) == 0); if (maybe_modified(old_iter, oitem, new_iter, nitem, diff) < 0 || git_iterator_advance(old_iter, &oitem) < 0 || @@ -863,222 +742,105 @@ static int diff_from_iterators( } } - git_iterator_free(old_iter); - git_iterator_free(new_iter); - git_buf_free(&ignore_prefix); - *diff_ptr = diff; - return 0; fail: - git_iterator_free(old_iter); - git_iterator_free(new_iter); + if (!*diff_ptr) { + git_diff_list_free(diff); + error = -1; + } + git_buf_free(&ignore_prefix); - git_diff_list_free(diff); - *diff_ptr = NULL; - return -1; + return error; } +#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \ + git_iterator *a = NULL, *b = NULL; \ + char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \ + if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + error = diff_from_iterators(diff, repo, a, b, opts); \ + git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ + } while (0) + int git_diff_tree_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, /**< can be NULL for defaults */ git_tree *old_tree, git_tree *new_tree, - git_diff_list **diff) + const git_diff_options *opts) { - git_iterator *a = NULL, *b = NULL; - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; - - assert(repo && old_tree && new_tree && diff); + int error = 0; - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || - git_iterator_for_tree_range(&b, repo, new_tree, prefix, prefix) < 0) - return -1; + assert(diff && repo); - git__free(prefix); + DIFF_FROM_ITERATORS( + git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), + git_iterator_for_tree_range(&b, repo, new_tree, pfx, pfx) + ); - return diff_from_iterators(repo, opts, a, b, diff); + return error; } int git_diff_index_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, git_tree *old_tree, - git_diff_list **diff) + git_index *index, + const git_diff_options *opts) { - git_iterator *a = NULL, *b = NULL; - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; - - assert(repo && diff); + int error = 0; - if (git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix) < 0 || - git_iterator_for_index_range(&b, repo, prefix, prefix) < 0) - goto on_error; + assert(diff && repo); - git__free(prefix); + if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + return error; - return diff_from_iterators(repo, opts, a, b, diff); + DIFF_FROM_ITERATORS( + git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), + git_iterator_for_index_range(&b, index, pfx, pfx) + ); -on_error: - git__free(prefix); - git_iterator_free(a); - return -1; + return error; } int git_diff_workdir_to_index( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, - git_diff_list **diff) + git_index *index, + const git_diff_options *opts) { - git_iterator *a = NULL, *b = NULL; - int error; - - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; - - assert(repo && diff); + int error = 0; - if ((error = git_iterator_for_index_range(&a, repo, prefix, prefix)) < 0 || - (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0) - goto on_error; + assert(diff && repo); - git__free(prefix); + if (!index && (error = git_repository_index__weakptr(&index, repo)) < 0) + return error; - return diff_from_iterators(repo, opts, a, b, diff); + DIFF_FROM_ITERATORS( + git_iterator_for_index_range(&a, index, pfx, pfx), + git_iterator_for_workdir_range(&b, repo, pfx, pfx) + ); -on_error: - git__free(prefix); - git_iterator_free(a); return error; } int git_diff_workdir_to_tree( + git_diff_list **diff, git_repository *repo, - const git_diff_options *opts, git_tree *old_tree, - git_diff_list **diff) -{ - git_iterator *a = NULL, *b = NULL; - int error; - - char *prefix = opts ? diff_prefix_from_pathspec(&opts->pathspec) : NULL; - - assert(repo && old_tree && diff); - - if ((error = git_iterator_for_tree_range(&a, repo, old_tree, prefix, prefix)) < 0 || - (error = git_iterator_for_workdir_range(&b, repo, prefix, prefix)) < 0) - goto on_error; - - git__free(prefix); - - return diff_from_iterators(repo, opts, a, b, diff); - -on_error: - git__free(prefix); - git_iterator_free(a); - return error; -} - - -bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta) -{ - uint32_t flags = opts ? opts->flags : 0; - - if (delta->status == GIT_DELTA_UNMODIFIED && - (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - return true; - - if (delta->status == GIT_DELTA_IGNORED && - (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNTRACKED && - (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - return true; - - return false; -} - - -int git_diff_merge( - git_diff_list *onto, - const git_diff_list *from) + const git_diff_options *opts) { int error = 0; - git_pool onto_pool; - git_vector onto_new; - git_diff_delta *delta; - bool ignore_case = false; - unsigned int i, j; - - assert(onto && from); - if (!from->deltas.length) - return 0; - - if (git_vector_init(&onto_new, onto->deltas.length, diff_delta__cmp) < 0 || - git_pool_init(&onto_pool, 1, 0) < 0) - return -1; + assert(diff && repo); - if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 || - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) - { - ignore_case = true; - - /* This function currently only supports merging diff lists that - * are sorted identically. */ - assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && - (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0); - } - - for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { - git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); - const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); - int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); - - if (cmp < 0) { - delta = diff_delta__dup(o, &onto_pool); - i++; - } else if (cmp > 0) { - delta = diff_delta__dup(f, &onto_pool); - j++; - } else { - delta = diff_delta__merge_like_cgit(o, f, &onto_pool); - i++; - j++; - } - - /* the ignore rules for the target may not match the source - * or the result of a merged delta could be skippable... - */ - if (git_diff_delta__should_skip(&onto->opts, delta)) { - git__free(delta); - continue; - } - - if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) - break; - } - - if (!error) { - git_vector_swap(&onto->deltas, &onto_new); - git_pool_swap(&onto->pool, &onto_pool); - onto->new_src = from->new_src; - - /* prefix strings also come from old pool, so recreate those.*/ - onto->opts.old_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); - onto->opts.new_prefix = - git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); - } - - git_vector_foreach(&onto_new, i, delta) - git__free(delta); - git_vector_free(&onto_new); - git_pool_clear(&onto_pool); + DIFF_FROM_ITERATORS( + git_iterator_for_tree_range(&a, repo, old_tree, pfx, pfx), + git_iterator_for_workdir_range(&b, repo, pfx, pfx) + ); return error; } diff --git a/src/diff.h b/src/diff.h index c6a26aee7..1e3be7593 100644 --- a/src/diff.h +++ b/src/diff.h @@ -28,6 +28,9 @@ enum { GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ }; +#define GIT_DELTA__TO_DELETE 10 +#define GIT_DELTA__TO_SPLIT 11 + struct git_diff_list { git_refcount rc; git_repository *repo; @@ -38,6 +41,11 @@ struct git_diff_list { git_iterator_type_t old_src; git_iterator_type_t new_src; uint32_t diffcaps; + + 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); }; extern void git_diff__cleanup_modes( @@ -45,8 +53,13 @@ extern void git_diff__cleanup_modes( extern void git_diff_list_addref(git_diff_list *diff); +extern int git_diff_delta__cmp(const void *a, const void *b); + extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); +extern int git_diff__oid_for_file( + git_repository *, const char *, uint16_t, git_off_t, git_oid *); + #endif diff --git a/src/diff_output.c b/src/diff_output.c index 5f0d13c64..46a9e02bf 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -120,7 +120,7 @@ static int diff_delta_is_binary_by_attr( return -1; mirror_new = (delta->new_file.path == delta->old_file.path || - strcmp(delta->new_file.path, delta->old_file.path) == 0); + ctxt->diff->strcomp(delta->new_file.path, delta->old_file.path) == 0); if (mirror_new) delta->new_file.flags |= (delta->old_file.flags & KNOWN_BINARY_FLAGS); else @@ -1002,7 +1002,7 @@ static int print_compact( git_buf_clear(pi->buf); if (delta->old_file.path != delta->new_file.path && - strcmp(delta->old_file.path,delta->new_file.path) != 0) + pi->diff->strcomp(delta->old_file.path,delta->new_file.path) != 0) git_buf_printf(pi->buf, "%c\t%s%c -> %s%c\n", code, delta->old_file.path, old_suffix, delta->new_file.path, new_suffix); else if (delta->old_file.mode != delta->new_file.mode && @@ -1502,3 +1502,129 @@ notfound: return GIT_ENOTFOUND; } +static int print_to_buffer_cb( + void *cb_data, + const git_diff_delta *delta, + const git_diff_range *range, + char line_origin, + const char *content, + size_t content_len) +{ + git_buf *output = cb_data; + GIT_UNUSED(delta); GIT_UNUSED(range); GIT_UNUSED(line_origin); + return git_buf_put(output, content, content_len); +} + +int git_diff_patch_print( + git_diff_patch *patch, + void *cb_data, + git_diff_data_fn print_cb) +{ + int error; + git_buf temp = GIT_BUF_INIT; + diff_print_info pi; + size_t h, l; + + assert(patch && print_cb); + + pi.diff = patch->diff; + pi.print_cb = print_cb; + pi.cb_data = cb_data; + pi.buf = &temp; + + error = print_patch_file(&pi, patch->delta, 0); + + for (h = 0; h < patch->hunks_size && !error; ++h) { + diff_patch_hunk *hunk = &patch->hunks[h]; + + error = print_patch_hunk(&pi, patch->delta, + &hunk->range, hunk->header, hunk->header_len); + + for (l = 0; l < hunk->line_count && !error; ++l) { + diff_patch_line *line = &patch->lines[hunk->line_start + l]; + + error = print_patch_line( + &pi, patch->delta, &hunk->range, + line->origin, line->ptr, line->len); + } + } + + git_buf_free(&temp); + + return error; +} + +int git_diff_patch_to_str( + char **string, + git_diff_patch *patch) +{ + int error; + git_buf output = GIT_BUF_INIT; + + error = git_diff_patch_print(patch, &output, print_to_buffer_cb); + + /* GIT_EUSER means git_buf_put in print_to_buffer_cb returned -1, + * meaning a memory allocation failure, so just map to -1... + */ + if (error == GIT_EUSER) + error = -1; + + *string = git_buf_detach(&output); + + return error; +} + +int git_diff__paired_foreach( + git_diff_list *idx2head, + git_diff_list *wd2idx, + int (*cb)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i), + void *cbref) +{ + int cmp; + git_diff_delta *i2h, *w2i; + size_t i, j, i_max, j_max; + bool icase = false; + + i_max = idx2head ? idx2head->deltas.length : 0; + j_max = wd2idx ? wd2idx->deltas.length : 0; + + if (idx2head && wd2idx && + (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) || + 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE))) + { + /* Then use the ignore-case sorter... */ + icase = true; + + /* and assert that both are ignore-case sorted. If this function + * ever needs to support merge joining result sets that are not sorted + * by the same function, then it will need to be extended to do a spool + * and sort on one of the results before merge joining */ + assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) && + 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)); + } + + for (i = 0, j = 0; i < i_max || j < j_max; ) { + i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; + w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; + + cmp = !w2i ? -1 : !i2h ? 1 : + STRCMP_CASESELECT(icase, i2h->old_file.path, w2i->old_file.path); + + if (cmp < 0) { + if (cb(cbref, i2h, NULL)) + return GIT_EUSER; + i++; + } else if (cmp > 0) { + if (cb(cbref, NULL, w2i)) + return GIT_EUSER; + j++; + } else { + if (cb(cbref, i2h, w2i)) + return GIT_EUSER; + i++; j++; + } + } + + return 0; +} + diff --git a/src/diff_output.h b/src/diff_output.h index 5fed1d998..f74dd3a71 100644 --- a/src/diff_output.h +++ b/src/diff_output.h @@ -83,4 +83,10 @@ typedef struct { uint32_t diffed : 1; } diff_delta_context; +extern int git_diff__paired_foreach( + git_diff_list *idx2head, + git_diff_list *wd2idx, + int (*cb)(void *cbref, git_diff_delta *i2h, git_diff_delta *w2i), + void *cbref); + #endif diff --git a/src/diff_tform.c b/src/diff_tform.c new file mode 100644 index 000000000..987d4b8e6 --- /dev/null +++ b/src/diff_tform.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "diff.h" +#include "git2/config.h" + +static git_diff_delta *diff_delta__dup( + const git_diff_delta *d, git_pool *pool) +{ + git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); + if (!delta) + return NULL; + + memcpy(delta, d, sizeof(git_diff_delta)); + + delta->old_file.path = git_pool_strdup(pool, d->old_file.path); + if (delta->old_file.path == NULL) + goto fail; + + if (d->new_file.path != d->old_file.path) { + delta->new_file.path = git_pool_strdup(pool, d->new_file.path); + if (delta->new_file.path == NULL) + goto fail; + } else { + delta->new_file.path = delta->old_file.path; + } + + return delta; + +fail: + git__free(delta); + return NULL; +} + +static git_diff_delta *diff_delta__merge_like_cgit( + const git_diff_delta *a, const git_diff_delta *b, git_pool *pool) +{ + git_diff_delta *dup; + + /* Emulate C git for merging two diffs (a la 'git diff <sha>'). + * + * When C git does a diff between the work dir and a tree, it actually + * diffs with the index but uses the workdir contents. This emulates + * those choices so we can emulate the type of diff. + * + * We have three file descriptions here, let's call them: + * f1 = a->old_file + * f2 = a->new_file AND b->old_file + * f3 = b->new_file + */ + + /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ + if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) + return diff_delta__dup(a, pool); + + /* otherwise, base this diff on the 'b' diff */ + if ((dup = diff_delta__dup(b, pool)) == NULL) + return NULL; + + /* If 'a' status is uninteresting, then we're done */ + if (a->status == GIT_DELTA_UNMODIFIED) + return dup; + + assert(a->status != GIT_DELTA_UNMODIFIED); + assert(b->status != GIT_DELTA_UNMODIFIED); + + /* A cgit exception is that the diff of a file that is only in the + * index (i.e. not in HEAD nor workdir) is given as empty. + */ + if (dup->status == GIT_DELTA_DELETED) { + if (a->status == GIT_DELTA_ADDED) + dup->status = GIT_DELTA_UNMODIFIED; + /* else don't overwrite DELETE status */ + } else { + dup->status = a->status; + } + + git_oid_cpy(&dup->old_file.oid, &a->old_file.oid); + dup->old_file.mode = a->old_file.mode; + dup->old_file.size = a->old_file.size; + dup->old_file.flags = a->old_file.flags; + + return dup; +} + +int git_diff_merge( + git_diff_list *onto, + const git_diff_list *from) +{ + int error = 0; + git_pool onto_pool; + git_vector onto_new; + git_diff_delta *delta; + bool ignore_case = false; + unsigned int i, j; + + assert(onto && from); + + if (!from->deltas.length) + return 0; + + if (git_vector_init( + &onto_new, onto->deltas.length, git_diff_delta__cmp) < 0 || + git_pool_init(&onto_pool, 1, 0) < 0) + return -1; + + if ((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 || + (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0) + { + ignore_case = true; + + /* This function currently only supports merging diff lists that + * are sorted identically. */ + assert((onto->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0 && + (from->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0); + } + + for (i = 0, j = 0; i < onto->deltas.length || j < from->deltas.length; ) { + git_diff_delta *o = GIT_VECTOR_GET(&onto->deltas, i); + const git_diff_delta *f = GIT_VECTOR_GET(&from->deltas, j); + int cmp = !f ? -1 : !o ? 1 : STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); + + if (cmp < 0) { + delta = diff_delta__dup(o, &onto_pool); + i++; + } else if (cmp > 0) { + delta = diff_delta__dup(f, &onto_pool); + j++; + } else { + delta = diff_delta__merge_like_cgit(o, f, &onto_pool); + i++; + j++; + } + + /* the ignore rules for the target may not match the source + * or the result of a merged delta could be skippable... + */ + if (git_diff_delta__should_skip(&onto->opts, delta)) { + git__free(delta); + continue; + } + + if ((error = !delta ? -1 : git_vector_insert(&onto_new, delta)) < 0) + break; + } + + if (!error) { + git_vector_swap(&onto->deltas, &onto_new); + git_pool_swap(&onto->pool, &onto_pool); + onto->new_src = from->new_src; + + /* prefix strings also come from old pool, so recreate those.*/ + onto->opts.old_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); + onto->opts.new_prefix = + git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); + } + + git_vector_foreach(&onto_new, i, delta) + git__free(delta); + git_vector_free(&onto_new); + git_pool_clear(&onto_pool); + + return error; +} + +#define DEFAULT_THRESHOLD 50 +#define DEFAULT_BREAK_REWRITE_THRESHOLD 60 +#define DEFAULT_TARGET_LIMIT 200 + +static int normalize_find_opts( + git_diff_list *diff, + git_diff_find_options *opts, + git_diff_find_options *given) +{ + git_config *cfg = NULL; + const char *val; + + if (diff->repo != NULL && + git_repository_config__weakptr(&cfg, diff->repo) < 0) + return -1; + + if (given != NULL) + memcpy(opts, given, sizeof(*opts)); + else { + memset(opts, 0, sizeof(*opts)); + + opts->flags = GIT_DIFF_FIND_RENAMES; + + if (git_config_get_string(&val, cfg, "diff.renames") < 0) + giterr_clear(); + else if (val && + (!strcasecmp(val, "copies") || !strcasecmp(val, "copy"))) + opts->flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES; + } + + /* some flags imply others */ + + if (opts->flags & GIT_DIFF_FIND_RENAMES_FROM_REWRITES) + opts->flags |= GIT_DIFF_FIND_RENAMES; + + if (opts->flags & GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED) + opts->flags |= GIT_DIFF_FIND_COPIES; + +#define USE_DEFAULT(X) ((X) == 0 || (X) > 100) + + if (USE_DEFAULT(opts->rename_threshold)) + opts->rename_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->rename_from_rewrite_threshold)) + opts->rename_from_rewrite_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->copy_threshold)) + opts->copy_threshold = DEFAULT_THRESHOLD; + + if (USE_DEFAULT(opts->break_rewrite_threshold)) + opts->break_rewrite_threshold = DEFAULT_BREAK_REWRITE_THRESHOLD; + +#undef USE_DEFAULT + + if (!opts->target_limit) { + int32_t limit = 0; + + opts->target_limit = DEFAULT_TARGET_LIMIT; + + if (git_config_get_int32(&limit, cfg, "diff.renameLimit") < 0) + giterr_clear(); + else if (limit > 0) + opts->target_limit = limit; + } + + return 0; +} + +static int apply_splits_and_deletes(git_diff_list *diff, size_t expected_size) +{ + git_vector onto = GIT_VECTOR_INIT; + size_t i; + git_diff_delta *delta; + + if (git_vector_init(&onto, expected_size, git_diff_delta__cmp) < 0) + return -1; + + /* build new delta list without TO_DELETE and splitting TO_SPLIT */ + git_vector_foreach(&diff->deltas, i, delta) { + if (delta->status == GIT_DELTA__TO_DELETE) { + git__free(delta); + continue; + } + + if (delta->status == GIT_DELTA__TO_SPLIT) { + git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool); + if (!deleted) + return -1; + + deleted->status = GIT_DELTA_DELETED; + memset(&deleted->new_file, 0, sizeof(deleted->new_file)); + deleted->new_file.path = deleted->old_file.path; + deleted->new_file.flags |= GIT_DIFF_FILE_VALID_OID; + + git_vector_insert(&onto, deleted); + + delta->status = GIT_DELTA_ADDED; + memset(&delta->old_file, 0, sizeof(delta->old_file)); + delta->old_file.path = delta->new_file.path; + delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + } + + git_vector_insert(&onto, delta); + } + + /* swap new delta list into place */ + git_vector_sort(&onto); + git_vector_swap(&diff->deltas, &onto); + git_vector_free(&onto); + + return 0; +} + +static unsigned int calc_similarity( + void *cache, git_diff_file *old_file, git_diff_file *new_file) +{ + GIT_UNUSED(cache); + + if (git_oid_cmp(&old_file->oid, &new_file->oid) == 0) + return 100; + + /* TODO: insert actual similarity algo here */ + + return 0; +} + +#define FLAG_SET(opts,flag_name) ((opts.flags & flag_name) != 0) + +int git_diff_find_similar( + git_diff_list *diff, + git_diff_find_options *given_opts) +{ + unsigned int i, j, similarity; + git_diff_delta *from, *to; + git_diff_find_options opts; + unsigned int tried_targets, num_changes = 0; + git_vector matches = GIT_VECTOR_INIT; + + if (normalize_find_opts(diff, &opts, given_opts) < 0) + return -1; + + /* first do splits if requested */ + + if (FLAG_SET(opts, GIT_DIFF_FIND_AND_BREAK_REWRITES)) { + git_vector_foreach(&diff->deltas, i, from) { + if (from->status != GIT_DELTA_MODIFIED) + continue; + + /* Right now, this doesn't work right because the similarity + * algorithm isn't actually implemented... + */ + similarity = 100; + /* calc_similarity(NULL, &from->old_file, from->new_file); */ + + if (similarity < opts.break_rewrite_threshold) { + from->status = GIT_DELTA__TO_SPLIT; + num_changes++; + } + } + + /* apply splits as needed */ + if (num_changes > 0 && + apply_splits_and_deletes( + diff, diff->deltas.length + num_changes) < 0) + return -1; + } + + /* next find the most similar delta for each rename / copy candidate */ + + if (git_vector_init(&matches, diff->deltas.length, git_diff_delta__cmp) < 0) + return -1; + + git_vector_foreach(&diff->deltas, i, from) { + tried_targets = 0; + + git_vector_foreach(&diff->deltas, j, to) { + if (i == j) + continue; + + switch (to->status) { + case GIT_DELTA_ADDED: + case GIT_DELTA_UNTRACKED: + case GIT_DELTA_RENAMED: + case GIT_DELTA_COPIED: + break; + default: + /* only the above status values should be checked */ + continue; + } + + /* skip all but DELETED files unless copy detection is on */ + if (from->status != GIT_DELTA_DELETED && + !FLAG_SET(opts, GIT_DIFF_FIND_COPIES)) + continue; + + /* don't check UNMODIFIED files as source unless given option */ + if (from->status == GIT_DELTA_UNMODIFIED && + !FLAG_SET(opts, GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED)) + continue; + + /* cap on maximum files we'll examine */ + if (++tried_targets > opts.target_limit) + break; + + /* calculate similarity and see if this pair beats the + * similarity score of the current best pair. + */ + similarity = calc_similarity(NULL, &from->old_file, &to->new_file); + + if (to->similarity < similarity) { + to->similarity = similarity; + if (git_vector_set(NULL, &matches, j, from) < 0) + return -1; + } + } + } + + /* next rewrite the diffs with renames / copies */ + + num_changes = 0; + + git_vector_foreach(&diff->deltas, j, to) { + from = GIT_VECTOR_GET(&matches, j); + if (!from) { + assert(to->similarity == 0); + continue; + } + + /* three possible outcomes here: + * 1. old DELETED and if over rename threshold, + * new becomes RENAMED and old goes away + * 2. old was MODIFIED but FIND_RENAMES_FROM_REWRITES is on and + * old is more similar to new than it is to itself, in which + * case, new becomes RENAMED and old becomed ADDED + * 3. otherwise if over copy threshold, new becomes COPIED + */ + + if (from->status == GIT_DELTA_DELETED) { + if (to->similarity < opts.rename_threshold) { + to->similarity = 0; + continue; + } + + to->status = GIT_DELTA_RENAMED; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + + from->status = GIT_DELTA__TO_DELETE; + num_changes++; + + continue; + } + + if (from->status == GIT_DELTA_MODIFIED && + FLAG_SET(opts, GIT_DIFF_FIND_RENAMES_FROM_REWRITES) && + to->similarity > opts.rename_threshold) + { + similarity = 100; + /* calc_similarity(NULL, &from->old_file, from->new_file); */ + + if (similarity < opts.rename_from_rewrite_threshold) { + to->status = GIT_DELTA_RENAMED; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + + from->status = GIT_DELTA_ADDED; + memset(&from->old_file, 0, sizeof(from->old_file)); + from->old_file.path = to->old_file.path; + from->old_file.flags |= GIT_DIFF_FILE_VALID_OID; + + continue; + } + } + + if (to->similarity < opts.copy_threshold) { + to->similarity = 0; + continue; + } + + /* convert "to" to a COPIED record */ + to->status = GIT_DELTA_COPIED; + memcpy(&to->old_file, &from->old_file, sizeof(to->old_file)); + } + + git_vector_free(&matches); + + if (num_changes > 0) { + assert(num_changes < diff->deltas.length); + + if (apply_splits_and_deletes( + diff, diff->deltas.length - num_changes) < 0) + return -1; + } + + return 0; +} + +#undef FLAG_SET diff --git a/src/errors.c b/src/errors.c index 942a2f799..ac7fa934d 100644 --- a/src/errors.c +++ b/src/errors.c @@ -40,52 +40,41 @@ void giterr_set(int error_class, const char *string, ...) { git_buf buf = GIT_BUF_INIT; va_list arglist; - - int unix_error_code = 0; - #ifdef GIT_WIN32 - DWORD win32_error_code = 0; + DWORD win32_error_code = (error_class == GITERR_OS) ? GetLastError() : 0; #endif - - if (error_class == GITERR_OS) { - unix_error_code = errno; - errno = 0; - -#ifdef GIT_WIN32 - win32_error_code = GetLastError(); - SetLastError(0); -#endif - } + int error_code = (error_class == GITERR_OS) ? errno : 0; va_start(arglist, string); git_buf_vprintf(&buf, string, arglist); va_end(arglist); - /* automatically suffix strerror(errno) for GITERR_OS errors */ if (error_class == GITERR_OS) { - - if (unix_error_code != 0) { - git_buf_PUTS(&buf, ": "); - git_buf_puts(&buf, strerror(unix_error_code)); - } - #ifdef GIT_WIN32 - else if (win32_error_code != 0) { - LPVOID lpMsgBuf = NULL; - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, win32_error_code, 0, (LPTSTR) &lpMsgBuf, 0, NULL); - - if (lpMsgBuf) { + if (win32_error_code) { + char *lpMsgBuf; + + if (FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, win32_error_code, 0, (LPSTR)&lpMsgBuf, 0, NULL)) { git_buf_PUTS(&buf, ": "); git_buf_puts(&buf, lpMsgBuf); LocalFree(lpMsgBuf); } + + SetLastError(0); } + else #endif + if (error_code) { + git_buf_PUTS(&buf, ": "); + git_buf_puts(&buf, strerror(error_code)); + } + + if (error_code) + errno = 0; } if (!git_buf_oom(&buf)) diff --git a/src/fetch.c b/src/fetch.c index dc01f6791..81136fc5f 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -8,16 +8,14 @@ #include "git2/oid.h" #include "git2/refs.h" #include "git2/revwalk.h" -#include "git2/indexer.h" +#include "git2/transport.h" #include "common.h" -#include "transport.h" #include "remote.h" #include "refspec.h" #include "pack.h" #include "fetch.h" #include "netops.h" -#include "pkt.h" struct filter_payload { git_remote *remote; @@ -86,61 +84,6 @@ cleanup: return error; } -/* Wait until we get an ack from the */ -static int recv_pkt(git_pkt **out, gitno_buffer *buf) -{ - const char *ptr = buf->data, *line_end = ptr; - git_pkt *pkt; - int pkt_type, error = 0, ret; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); - else - error = GIT_EBUFS; - - if (error == 0) - break; /* return the pkt */ - - if (error < 0 && error != GIT_EBUFS) - return -1; - - if ((ret = gitno_recv(buf)) < 0) - return -1; - } while (error); - - gitno_consume(buf, line_end); - pkt_type = pkt->type; - if (out != NULL) - *out = pkt; - else - git__free(pkt); - - return pkt_type; -} - -static int store_common(git_transport *t) -{ - git_pkt *pkt = NULL; - gitno_buffer *buf = &t->buffer; - - do { - if (recv_pkt(&pkt, buf) < 0) - return -1; - - if (pkt->type == GIT_PKT_ACK) { - if (git_vector_insert(&t->common, pkt) < 0) - return -1; - } else { - git__free(pkt); - return 0; - } - - } while (1); - - return 0; -} - /* * 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 @@ -149,13 +92,7 @@ static int store_common(git_transport *t) int git_fetch_negotiate(git_remote *remote) { git_transport *t = remote->transport; - gitno_buffer *buf = &t->buffer; - git_buf data = GIT_BUF_INIT; - git_revwalk *walk = NULL; - int error = -1, pkt_type; - unsigned int i; - git_oid oid; - + if (filter_wants(remote) < 0) { giterr_set(GITERR_NET, "Failed to filter the reference list for wants"); return -1; @@ -167,298 +104,23 @@ int git_fetch_negotiate(git_remote *remote) /* * Now we have everything set up so we can start tell the - * server what we want and what we have. Call the function if - * the transport has its own logic. This is transitional and - * will be removed once this function can support git and http. - */ - if (t->own_logic) - return t->negotiate_fetch(t, remote->repo, &remote->refs); - - /* No own logic, do our thing */ - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - return -1; - - if (git_fetch_setup_walk(&walk, remote->repo) < 0) - goto on_error; - /* - * We don't support any kind of ACK extensions, so the negotiation - * boils down to sending what we have and listening for an ACK - * every once in a while. + * server what we want and what we have. */ - i = 0; - while ((error = git_revwalk_next(&oid, walk)) == 0) { - git_pkt_buffer_have(&oid, &data); - i++; - if (i % 20 == 0) { - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - git_pkt_buffer_flush(&data); - if (git_buf_oom(&data)) - goto on_error; - - if (t->negotiation_step(t, data.ptr, data.size) < 0) - goto on_error; - - git_buf_clear(&data); - if (t->caps.multi_ack) { - if (store_common(t) < 0) - goto on_error; - } else { - pkt_type = recv_pkt(NULL, buf); - - if (pkt_type == GIT_PKT_ACK) { - break; - } else if (pkt_type == GIT_PKT_NAK) { - continue; - } else { - giterr_set(GITERR_NET, "Unexpected pkt type"); - goto on_error; - } - } - } - - if (t->common.length > 0) - break; - - if (i % 20 == 0 && t->rpc) { - git_pkt_ack *pkt; - unsigned int i; - - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - goto on_error; - - git_vector_foreach(&t->common, i, pkt) { - git_pkt_buffer_have(&pkt->oid, &data); - } - - if (git_buf_oom(&data)) - goto on_error; - } - } - - if (error < 0 && error != GIT_ITEROVER) - goto on_error; - - /* Tell the other end that we're done negotiating */ - if (t->rpc && t->common.length > 0) { - git_pkt_ack *pkt; - unsigned int i; - - if (git_pkt_buffer_wants(&remote->refs, &t->caps, &data) < 0) - goto on_error; - - git_vector_foreach(&t->common, i, pkt) { - git_pkt_buffer_have(&pkt->oid, &data); - } - - if (git_buf_oom(&data)) - goto on_error; - } - - git_pkt_buffer_done(&data); - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - if (t->negotiation_step(t, data.ptr, data.size) < 0) - goto on_error; - - git_buf_free(&data); - git_revwalk_free(walk); - - /* Now let's eat up whatever the server gives us */ - if (!t->caps.multi_ack) { - pkt_type = recv_pkt(NULL, buf); - if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { - giterr_set(GITERR_NET, "Unexpected pkt type"); - return -1; - } - } else { - git_pkt_ack *pkt; - do { - if (recv_pkt((git_pkt **)&pkt, buf) < 0) - return -1; - - if (pkt->type == GIT_PKT_NAK || - (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { - git__free(pkt); - break; - } - - git__free(pkt); - } while (1); - } - - return 0; - -on_error: - git_revwalk_free(walk); - git_buf_free(&data); - return error; + 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, git_off_t *bytes, git_indexer_stats *stats) +int git_fetch_download_pack( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload) { git_transport *t = remote->transport; if(!remote->need_pack) return 0; - if (t->own_logic) - return t->download_pack(t, remote->repo, bytes, stats); - - return git_fetch__download_pack(t, remote->repo, bytes, stats); - -} - -static int no_sideband(git_transport *t, git_indexer_stream *idx, gitno_buffer *buf, git_off_t *bytes, git_indexer_stats *stats) -{ - int recvd; - - do { - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - return GIT_EUSER; - } - - if (git_indexer_stream_add(idx, buf->data, buf->offset, stats) < 0) - return -1; - - gitno_consume_n(buf, buf->offset); - - if ((recvd = gitno_recv(buf)) < 0) - return -1; - - *bytes += recvd; - } while(recvd > 0); - - if (git_indexer_stream_finalize(idx, stats)) - return -1; - - return 0; -} - -/* Receiving data from a socket and storing it is pretty much the same for git and HTTP */ -int git_fetch__download_pack( - git_transport *t, - git_repository *repo, - git_off_t *bytes, - git_indexer_stats *stats) -{ - git_buf path = GIT_BUF_INIT; - gitno_buffer *buf = &t->buffer; - git_indexer_stream *idx = NULL; - int error = -1; - - if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0) - return -1; - - if (git_indexer_stream_new(&idx, git_buf_cstr(&path)) < 0) - goto on_error; - - git_buf_free(&path); - memset(stats, 0, sizeof(git_indexer_stats)); - *bytes = 0; - - /* - * If the remote doesn't support the side-band, we can feed - * the data directly to the indexer. Otherwise, we need to - * check which one belongs there. - */ - if (!t->caps.side_band && !t->caps.side_band_64k) { - if (no_sideband(t, idx, buf, bytes, stats) < 0) - goto on_error; - - git_indexer_stream_free(idx); - return 0; - } - - do { - git_pkt *pkt; - - if (t->cancel.val) { - giterr_set(GITERR_NET, "The fetch was cancelled by the user"); - error = GIT_EUSER; - goto on_error; - } - - if (recv_pkt(&pkt, buf) < 0) - goto on_error; - - if (pkt->type == GIT_PKT_PROGRESS) { - if (t->progress_cb) { - git_pkt_progress *p = (git_pkt_progress *) pkt; - t->progress_cb(p->data, p->len, t->cb_data); - } - git__free(pkt); - } else if (pkt->type == GIT_PKT_DATA) { - git_pkt_data *p = (git_pkt_data *) pkt; - *bytes += p->len; - if (git_indexer_stream_add(idx, p->data, p->len, stats) < 0) - goto on_error; - - git__free(pkt); - } else if (pkt->type == GIT_PKT_FLUSH) { - /* A flush indicates the end of the packfile */ - git__free(pkt); - break; - } - } while (1); - - if (git_indexer_stream_finalize(idx, stats) < 0) - goto on_error; - - git_indexer_stream_free(idx); - return 0; - -on_error: - git_buf_free(&path); - git_indexer_stream_free(idx); - return error; -} - -int git_fetch_setup_walk(git_revwalk **out, git_repository *repo) -{ - git_revwalk *walk; - git_strarray refs; - unsigned int i; - git_reference *ref; - - if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0) - return -1; - - if (git_revwalk_new(&walk, repo) < 0) - return -1; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - - for (i = 0; i < refs.count; ++i) { - /* No tags */ - if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) - continue; - - if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) - goto on_error; - - if (git_reference_type(ref) == GIT_REF_SYMBOLIC) - continue; - if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) - goto on_error; - - git_reference_free(ref); - } - - git_strarray_free(&refs); - *out = walk; - return 0; - -on_error: - git_reference_free(ref); - git_strarray_free(&refs); - return -1; + return t->download_pack(t, remote->repo, &remote->stats, progress_cb, progress_payload); } diff --git a/src/fetch.h b/src/fetch.h index 87bb43b07..5b8c20665 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -10,9 +10,19 @@ #include "netops.h" int git_fetch_negotiate(git_remote *remote); -int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); -int git_fetch__download_pack(git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); +int git_fetch_download_pack( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload); + +int git_fetch__download_pack( + git_transport *t, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload); + int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); #endif diff --git a/src/fetchhead.c b/src/fetchhead.c new file mode 100644 index 000000000..ed47bab48 --- /dev/null +++ b/src/fetchhead.c @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2/types.h" +#include "git2/oid.h" + +#include "fetchhead.h" +#include "common.h" +#include "fileops.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; + + return strcmp(one->ref_name, two->ref_name); +} + +int git_fetchhead_ref_create( + git_fetchhead_ref **fetchhead_ref_out, + git_oid *oid, + int is_merge, + const char *ref_name, + const char *remote_url) +{ + git_fetchhead_ref *fetchhead_ref = NULL; + + assert(fetchhead_ref_out && oid && ref_name && remote_url); + + fetchhead_ref = git__malloc(sizeof(git_fetchhead_ref)); + GITERR_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; + fetchhead_ref->ref_name = git__strdup(ref_name); + fetchhead_ref->remote_url = git__strdup(remote_url); + + *fetchhead_ref_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; + + 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 { + type = ""; + name = fetchhead_ref->ref_name; + } + + 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->path_repository, GIT_FETCH_HEAD_FILE) < 0) + return -1; + + if (git_filebuf_open(&file, path.ptr, GIT_FILEBUF_FORCE) < 0) { + git_buf_free(&path); + return -1; + } + + git_buf_free(&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, GIT_REFS_FILE_MODE); +} + +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); +} + diff --git a/src/fetchhead.h b/src/fetchhead.h new file mode 100644 index 000000000..ec7c1985b --- /dev/null +++ b/src/fetchhead.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_fetchhead_h__ +#define INCLUDE_fetchhead_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, 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 *repository, git_vector *fetchhead_refs); + +void git_fetchhead_ref_free(git_fetchhead_ref *fetchhead_ref); + +#endif diff --git a/src/filebuf.c b/src/filebuf.c index b9b908c8d..0eb5b458a 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -85,8 +85,8 @@ static int lock_file(git_filebuf *file, int flags) while ((read_bytes = p_read(source, buffer, sizeof(buffer))) > 0) { p_write(file->fd, buffer, read_bytes); - if (file->digest) - git_hash_update(file->digest, buffer, read_bytes); + if (file->compute_digest) + git_hash_update(&file->digest, buffer, read_bytes); } p_close(source); @@ -108,8 +108,10 @@ void git_filebuf_cleanup(git_filebuf *file) if (file->fd_is_open && file->path_lock && git_path_exists(file->path_lock)) p_unlink(file->path_lock); - if (file->digest) - git_hash_free_ctx(file->digest); + if (file->compute_digest) { + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; + } if (file->buffer) git__free(file->buffer); @@ -149,8 +151,8 @@ static int write_normal(git_filebuf *file, void *source, size_t len) return -1; } - if (file->digest) - git_hash_update(file->digest, source, len); + if (file->compute_digest) + git_hash_update(&file->digest, source, len); } return 0; @@ -186,8 +188,8 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) assert(zs->avail_in == 0); - if (file->digest) - git_hash_update(file->digest, source, len); + if (file->compute_digest) + git_hash_update(&file->digest, source, len); } return 0; @@ -221,8 +223,10 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags) /* If we are hashing on-write, allocate a new hash context */ if (flags & GIT_FILEBUF_HASH_CONTENTS) { - file->digest = git_hash_new_ctx(); - GITERR_CHECK_ALLOC(file->digest); + file->compute_digest = 1; + + if (git_hash_ctx_init(&file->digest) < 0) + goto cleanup; } compression = flags >> GIT_FILEBUF_DEFLATE_SHIFT; @@ -291,16 +295,16 @@ cleanup: int git_filebuf_hash(git_oid *oid, git_filebuf *file) { - assert(oid && file && file->digest); + 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_free_ctx(file->digest); - file->digest = NULL; + git_hash_final(oid, &file->digest); + git_hash_ctx_cleanup(&file->digest); + file->compute_digest = 0; return 0; } @@ -466,3 +470,26 @@ int git_filebuf_printf(git_filebuf *file, const char *format, ...) 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) { + giterr_set(GITERR_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; +} diff --git a/src/filebuf.h b/src/filebuf.h index 377883147..dcaad9bd8 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -31,7 +31,8 @@ struct git_filebuf { int (*write)(struct git_filebuf *file, void *source, size_t len); - git_hash_ctx *digest; + bool compute_digest; + git_hash_ctx digest; unsigned char *buffer; unsigned char *z_buf; @@ -82,5 +83,6 @@ int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); int git_filebuf_flush(git_filebuf *file); +int git_filebuf_stats(time_t *mtime, size_t *size, git_filebuf *file); #endif diff --git a/src/fileops.c b/src/fileops.c index 6342b1679..7f023bf69 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -14,7 +14,8 @@ int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( - file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST); + file_path, NULL, mode, + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } int git_futils_mktmp(git_buf *path_out, const char *filename) @@ -142,10 +143,11 @@ int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) } int git_futils_readbuffer_updated( - git_buf *buf, const char *path, time_t *mtime, int *updated) + git_buf *buf, const char *path, time_t *mtime, size_t *size, int *updated) { git_file fd; struct stat st; + bool changed = false; assert(buf && path && *path); @@ -162,16 +164,25 @@ int git_futils_readbuffer_updated( } /* - * If we were given a time, we only want to read the file if it - * has been modified. + * If we were given a time and/or a size, we only want to read the file + * if it has been modified. */ - if (mtime != NULL && *mtime >= st.st_mtime) { + if (size && *size != (size_t)st.st_size) + changed = true; + if (mtime && *mtime != st.st_mtime) + changed = true; + if (!size && !mtime) + changed = true; + + if (!changed) { p_close(fd); return 0; } if (mtime != NULL) *mtime = st.st_mtime; + if (size != NULL) + *size = (size_t)st.st_size; if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); @@ -188,7 +199,7 @@ int git_futils_readbuffer_updated( int git_futils_readbuffer(git_buf *buf, const char *path) { - return git_futils_readbuffer_updated(buf, path, NULL, NULL); + return git_futils_readbuffer_updated(buf, path, NULL, NULL, NULL); } int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode) @@ -240,6 +251,7 @@ int git_futils_mkdir( mode_t mode, uint32_t flags) { + int error = -1; git_buf make_path = GIT_BUF_INIT; ssize_t root = 0; char lastch, *tail; @@ -287,12 +299,28 @@ int git_futils_mkdir( *tail = '\0'; /* make directory */ - if (p_mkdir(make_path.ptr, mode) < 0 && - (errno != EEXIST || (flags & GIT_MKDIR_EXCL) != 0)) - { - giterr_set(GITERR_OS, "Failed to make directory '%s'", - make_path.ptr); - goto fail; + if (p_mkdir(make_path.ptr, mode) < 0) { + if (errno == EEXIST) { + if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0) { + if (!git_path_isdir(make_path.ptr)) { + giterr_set( + GITERR_OS, "Existing path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + goto fail; + } + } + if ((flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_OS, "Directory already exists '%s'", + make_path.ptr); + error = GIT_EEXISTS; + goto fail; + } + } else { + giterr_set(GITERR_OS, "Failed to make directory '%s'", + make_path.ptr); + goto fail; + } } /* chmod if requested */ @@ -314,7 +342,7 @@ int git_futils_mkdir( fail: git_buf_free(&make_path); - return -1; + return error; } int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) @@ -322,57 +350,145 @@ int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH); } -static int _rmdir_recurs_foreach(void *opaque, git_buf *path) +typedef struct { + const char *base; + uint32_t flags; + int error; +} futils__rmdir_data; + +static int futils__error_cannot_rmdir(const char *path, const char *filemsg) { - git_directory_removal_type removal_type = *(git_directory_removal_type *)opaque; + if (filemsg) + giterr_set(GITERR_OS, "Could not remove directory. File '%s' %s", + path, filemsg); + else + giterr_set(GITERR_OS, "Could not remove directory '%s'", path); - if (git_path_isdir(path->ptr) == true) { - if (git_path_direach(path, _rmdir_recurs_foreach, opaque) < 0) - return -1; + return -1; +} - if (p_rmdir(path->ptr) < 0) { - if (removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS && (errno == ENOTEMPTY || errno == EEXIST)) - return 0; +static int futils__rm_first_parent(git_buf *path, const char *ceiling) +{ + int error = GIT_ENOTFOUND; + struct stat st; - giterr_set(GITERR_OS, "Could not remove directory '%s'", path->ptr); - return -1; - } + while (error == GIT_ENOTFOUND) { + git_buf_rtruncate_at_char(path, '/'); + + if (!path->size || git__prefixcmp(path->ptr, ceiling) != 0) + error = 0; + else if (p_lstat_posixly(path->ptr, &st) == 0) { + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + error = p_unlink(path->ptr); + else if (!S_ISDIR(st.st_mode)) + error = -1; /* fail to remove non-regular file */ + } else if (errno != ENOTDIR) + error = -1; + } - return 0; + if (error) + futils__error_cannot_rmdir(path->ptr, "cannot remove parent"); + + return error; +} + +static int futils__rmdir_recurs_foreach(void *opaque, git_buf *path) +{ + struct stat st; + futils__rmdir_data *data = opaque; + + if ((data->error = p_lstat_posixly(path->ptr, &st)) < 0) { + if (errno == ENOENT) + data->error = 0; + else if (errno == ENOTDIR) { + /* asked to remove a/b/c/d/e and a/b is a normal file */ + if ((data->flags & GIT_RMDIR_REMOVE_BLOCKERS) != 0) + data->error = futils__rm_first_parent(path, data->base); + else + futils__error_cannot_rmdir( + path->ptr, "parent is not directory"); + } + else + futils__error_cannot_rmdir(path->ptr, "cannot access"); } - if (removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS) { - if (p_unlink(path->ptr) < 0) { - giterr_set(GITERR_OS, "Could not remove directory. File '%s' cannot be removed", path->ptr); - return -1; + else if (S_ISDIR(st.st_mode)) { + int error = git_path_direach(path, futils__rmdir_recurs_foreach, data); + if (error < 0) + return (error == GIT_EUSER) ? data->error : error; + + data->error = p_rmdir(path->ptr); + + if (data->error < 0) { + if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) != 0 && + (errno == ENOTEMPTY || errno == EEXIST)) + data->error = 0; + else + futils__error_cannot_rmdir(path->ptr, NULL); } + } - return 0; + else if ((data->flags & GIT_RMDIR_REMOVE_FILES) != 0) { + data->error = p_unlink(path->ptr); + + if (data->error < 0) + futils__error_cannot_rmdir(path->ptr, "cannot be removed"); } - if (removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY) { - giterr_set(GITERR_OS, "Could not remove directory. File '%s' still present", path->ptr); - return -1; + else if ((data->flags & GIT_RMDIR_SKIP_NONEMPTY) == 0) + data->error = futils__error_cannot_rmdir(path->ptr, "still present"); + + return data->error; +} + +static int futils__rmdir_empty_parent(void *opaque, git_buf *path) +{ + int error = p_rmdir(path->ptr); + + GIT_UNUSED(opaque); + + if (error) { + int en = errno; + + if (en == ENOENT || en == ENOTDIR) { + giterr_clear(); + error = 0; + } else if (en == ENOTEMPTY || en == EEXIST) { + giterr_clear(); + error = GIT_ITEROVER; + } else { + futils__error_cannot_rmdir(path->ptr, NULL); + } } - return 0; + return error; } int git_futils_rmdir_r( - const char *path, const char *base, git_directory_removal_type removal_type) + const char *path, const char *base, uint32_t flags) { int error; git_buf fullpath = GIT_BUF_INIT; - - assert(removal_type == GIT_DIRREMOVAL_EMPTY_HIERARCHY - || removal_type == GIT_DIRREMOVAL_FILES_AND_DIRS - || removal_type == GIT_DIRREMOVAL_ONLY_EMPTY_DIRS); + futils__rmdir_data data; /* build path and find "root" where we should start calling mkdir */ if (git_path_join_unrooted(&fullpath, path, base, NULL) < 0) return -1; - error = _rmdir_recurs_foreach(&removal_type, &fullpath); + data.base = base ? base : ""; + data.flags = flags; + data.error = 0; + + error = futils__rmdir_recurs_foreach(&data, &fullpath); + + /* remove now-empty parents if requested */ + if (!error && (flags & GIT_RMDIR_EMPTY_PARENTS) != 0) { + error = git_path_walk_up( + &fullpath, base, futils__rmdir_empty_parent, &data); + + if (error == GIT_ITEROVER) + error = 0; + } git_buf_free(&fullpath); @@ -660,3 +776,38 @@ int git_futils_cp_r( return error; } + +int git_futils_filestamp_check( + git_futils_filestamp *stamp, const char *path) +{ + struct stat st; + + /* if the stamp is NULL, then always reload */ + if (stamp == NULL) + return 1; + + if (p_stat(path, &st) < 0) + return GIT_ENOTFOUND; + + if (stamp->mtime == (git_time_t)st.st_mtime && + stamp->size == (git_off_t)st.st_size && + stamp->ino == (unsigned int)st.st_ino) + return 0; + + stamp->mtime = (git_time_t)st.st_mtime; + stamp->size = (git_off_t)st.st_size; + stamp->ino = (unsigned int)st.st_ino; + + return 1; +} + +void git_futils_filestamp_set( + git_futils_filestamp *target, const git_futils_filestamp *source) +{ + assert(target); + + if (source) + memcpy(target, source, sizeof(*target)); + else + memset(target, 0, sizeof(*target)); +} diff --git a/src/fileops.h b/src/fileops.h index 19f7ffd54..a74f8b758 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -18,7 +18,8 @@ * Read whole files into an in-memory buffer for processing */ extern int git_futils_readbuffer(git_buf *obj, const char *path); -extern int git_futils_readbuffer_updated(git_buf *obj, const char *path, time_t *mtime, int *updated); +extern int git_futils_readbuffer_updated( + git_buf *obj, const char *path, time_t *mtime, size_t *size, int *updated); extern int git_futils_readbuffer_fd(git_buf *obj, git_file fd, size_t len); /** @@ -64,6 +65,7 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m * * 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_VERIFY_DIR says confirm final item is a dir, not just EEXIST * * Note that the chmod options will be executed even if the directory already * exists, unless GIT_MKDIR_EXCL is given. @@ -73,7 +75,8 @@ typedef enum { GIT_MKDIR_PATH = 2, GIT_MKDIR_CHMOD = 4, GIT_MKDIR_CHMOD_PATH = 8, - GIT_MKDIR_SKIP_LAST = 16 + GIT_MKDIR_SKIP_LAST = 16, + GIT_MKDIR_VERIFY_DIR = 32, } git_futils_mkdir_flags; /** @@ -97,27 +100,40 @@ extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uin */ 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 + * + * The old values translate into the new as follows: + * + * * GIT_DIRREMOVAL_EMPTY_HIERARCHY == GIT_RMDIR_EMPTY_HIERARCHY + * * GIT_DIRREMOVAL_FILES_AND_DIRS ~= GIT_RMDIR_REMOVE_FILES + * * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS == GIT_RMDIR_SKIP_NONEMPTY + */ typedef enum { - GIT_DIRREMOVAL_EMPTY_HIERARCHY = 0, - GIT_DIRREMOVAL_FILES_AND_DIRS = 1, - GIT_DIRREMOVAL_ONLY_EMPTY_DIRS = 2, -} git_directory_removal_type; + 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_futils_rmdir_flags; /** * Remove path and any files and directories beneath it. * * @param path Path to to top level directory to process. * @param base Root for relative path. - * @param removal_type GIT_DIRREMOVAL_EMPTY_HIERARCHY to remove a hierarchy - * of empty directories (will fail if any file is found), - * GIT_DIRREMOVAL_FILES_AND_DIRS to remove a hierarchy of - * files and folders, - * GIT_DIRREMOVAL_ONLY_EMPTY_DIRS to only remove empty - * directories (no failure on file encounter). - * + * @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, git_directory_removal_type removal_type); +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. @@ -266,4 +282,44 @@ extern int git_futils_find_system_file(git_buf *path, const char *filename); */ 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 { + git_time_t mtime; + git_off_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 or + * 1 if the file has been changed. (This also may return GIT_ENOTFOUND if + * the file doesn't exist.) + * + * @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, <0 on error + */ +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); + #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c index 28a05235b..f2ab1b85a 100644 --- a/src/filter.c +++ b/src/filter.c @@ -33,10 +33,6 @@ void git_text_gather_stats(git_text_stats *stats, const git_buf *text) else if (c == '\n') stats->lf++; - else if (c == 0x85) - /* Unicode CR+LF */ - stats->crlf++; - else if (c == 127) /* DEL */ stats->nonprintable++; diff --git a/src/global.c b/src/global.c index 22127faf7..d085089c3 100644 --- a/src/global.c +++ b/src/global.c @@ -6,6 +6,7 @@ */ #include "common.h" #include "global.h" +#include "hash.h" #include "git2/threads.h" #include "thread-utils.h" @@ -38,19 +39,39 @@ git_mutex git__mwindow_mutex; * functions are not available in that case. */ +/* + * `git_threads_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_threads_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 _tls_index; static int _tls_init = 0; -void git_threads_init(void) +int git_threads_init(void) { + int error; + if (_tls_init) - return; + return 0; _tls_index = TlsAlloc(); - _tls_init = 1; git_mutex_init(&git__mwindow_mutex); + + /* Initialize any other subsystems that have global state */ + if ((error = git_hash_global_init()) >= 0) + _tls_init = 1; + + if (error == 0) + _tls_init = 1; + + GIT_MEMORY_BARRIER; + + return error; } void git_threads_shutdown(void) @@ -58,6 +79,9 @@ void git_threads_shutdown(void) TlsFree(_tls_index); _tls_init = 0; git_mutex_free(&git__mwindow_mutex); + + /* Shut down any subsystems that have global state */ + git_hash_global_shutdown(); } git_global_st *git__global_state(void) @@ -88,19 +112,31 @@ static void cb__free_status(void *st) git__free(st); } -void git_threads_init(void) +int git_threads_init(void) { + int error = 0; + if (_tls_init) - return; + return 0; pthread_key_create(&_tls_key, &cb__free_status); - _tls_init = 1; + + /* Initialize any other subsystems that have global state */ + if ((error = git_hash_global_init()) >= 0) + _tls_init = 1; + + GIT_MEMORY_BARRIER; + + return error; } void git_threads_shutdown(void) { pthread_key_delete(_tls_key); _tls_init = 0; + + /* Shut down any subsystems that have global state */ + git_hash_global_shutdown(); } git_global_st *git__global_state(void) @@ -125,9 +161,10 @@ git_global_st *git__global_state(void) static git_global_st __state; -void git_threads_init(void) +int git_threads_init(void) { /* noop */ + return 0; } void git_threads_shutdown(void) diff --git a/src/global.h b/src/global.h index 0ad41ee63..b117714a8 100644 --- a/src/global.h +++ b/src/global.h @@ -8,6 +8,15 @@ #define INCLUDE_global_h__ #include "mwindow.h" +#include "hash.h" + +#if defined(GIT_THREADS) && defined(_MSC_VER) +# define GIT_MEMORY_BARRIER MemoryBarrier() +#elif defined(GIT_THREADS) +# define GIT_MEMORY_BARRIER __sync_synchronize() +#else +# define GIT_MEMORY_BARRIER /* noop */ +#endif typedef struct { git_error *last_error; diff --git a/src/hash.c b/src/hash.c index 460756913..21db2e129 100644 --- a/src/hash.c +++ b/src/hash.c @@ -8,67 +8,40 @@ #include "common.h" #include "hash.h" -#if defined(PPC_SHA1) -# include "ppc/sha1.h" -#else -# include "sha1.h" -#endif - -struct git_hash_ctx { - SHA_CTX c; -}; - -git_hash_ctx *git_hash_new_ctx(void) +int git_hash_buf(git_oid *out, const void *data, size_t len) { - git_hash_ctx *ctx = git__malloc(sizeof(*ctx)); - - if (!ctx) - return NULL; - - SHA1_Init(&ctx->c); + git_hash_ctx ctx; + int error = 0; - return ctx; -} + if (git_hash_ctx_init(&ctx) < 0) + return -1; -void git_hash_free_ctx(git_hash_ctx *ctx) -{ - git__free(ctx); -} + if ((error = git_hash_update(&ctx, data, len)) >= 0) + error = git_hash_final(out, &ctx); -void git_hash_init(git_hash_ctx *ctx) -{ - assert(ctx); - SHA1_Init(&ctx->c); + git_hash_ctx_cleanup(&ctx); + + return error; } -void git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n) { - assert(ctx); - SHA1_Update(&ctx->c, data, len); -} + git_hash_ctx ctx; + size_t i; + int error = 0; -void git_hash_final(git_oid *out, git_hash_ctx *ctx) -{ - assert(ctx); - SHA1_Final(out->id, &ctx->c); -} + if (git_hash_ctx_init(&ctx) < 0) + return -1; -void git_hash_buf(git_oid *out, const void *data, size_t len) -{ - SHA_CTX c; + for (i = 0; i < n; i++) { + if ((error = git_hash_update(&ctx, vec[i].data, vec[i].len)) < 0) + goto done; + } - SHA1_Init(&c); - SHA1_Update(&c, data, len); - SHA1_Final(out->id, &c); -} + error = git_hash_final(out, &ctx); -void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n) -{ - SHA_CTX c; - size_t i; +done: + git_hash_ctx_cleanup(&ctx); - SHA1_Init(&c); - for (i = 0; i < n; i++) - SHA1_Update(&c, vec[i].data, vec[i].len); - SHA1_Final(out->id, &c); + return error; } diff --git a/src/hash.h b/src/hash.h index 33d7b20cd..127be282f 100644 --- a/src/hash.h +++ b/src/hash.h @@ -9,21 +9,35 @@ #include "git2/oid.h" +typedef struct git_hash_prov git_hash_prov; typedef struct git_hash_ctx git_hash_ctx; +int git_hash_global_init(void); +void git_hash_global_shutdown(void); + +int git_hash_ctx_init(git_hash_ctx *ctx); +void git_hash_ctx_cleanup(git_hash_ctx *ctx); + +#if defined(OPENSSL_SHA1) +# include "hash/hash_openssl.h" +#elif defined(WIN32_SHA1) +# include "hash/hash_win32.h" +#elif defined(PPC_SHA1) +# include "hash/hash_ppc.h" +#else +# include "hash/hash_generic.h" +#endif + typedef struct { void *data; size_t len; } git_buf_vec; -git_hash_ctx *git_hash_new_ctx(void); -void git_hash_free_ctx(git_hash_ctx *ctx); - -void git_hash_init(git_hash_ctx *c); -void git_hash_update(git_hash_ctx *c, const void *data, size_t len); -void git_hash_final(git_oid *out, git_hash_ctx *c); +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); -void git_hash_buf(git_oid *out, const void *data, size_t len); -void git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n); +int git_hash_buf(git_oid *out, const void *data, size_t len); +int git_hash_vec(git_oid *out, git_buf_vec *vec, size_t n); #endif /* INCLUDE_hash_h__ */ diff --git a/src/sha1/sha1.c b/src/hash/hash_generic.c index 8aaedeb8f..30d7a5d1e 100644 --- a/src/sha1/sha1.c +++ b/src/hash/hash_generic.c @@ -6,7 +6,8 @@ */ #include "common.h" -#include "sha1.h" +#include "hash.h" +#include "hash/hash_generic.h" #if defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) @@ -112,7 +113,7 @@ #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 blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) +static void hash__block(git_hash_ctx *ctx, const unsigned int *data) { unsigned int A,B,C,D,E; unsigned int array[16]; @@ -220,7 +221,7 @@ static void blk_SHA1_Block(blk_SHA_CTX *ctx, const unsigned int *data) ctx->H[4] += E; } -void git__blk_SHA1_Init(blk_SHA_CTX *ctx) +int git_hash_init(git_hash_ctx *ctx) { ctx->size = 0; @@ -230,9 +231,11 @@ void git__blk_SHA1_Init(blk_SHA_CTX *ctx) ctx->H[2] = 0x98badcfe; ctx->H[3] = 0x10325476; ctx->H[4] = 0xc3d2e1f0; + + return 0; } -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len) +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) { unsigned int lenW = ctx->size & 63; @@ -248,19 +251,21 @@ void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len) len -= left; data = ((const char *)data + left); if (lenW) - return; - blk_SHA1_Block(ctx, ctx->W); + return 0; + hash__block(ctx, ctx->W); } while (len >= 64) { - blk_SHA1_Block(ctx, data); + hash__block(ctx, data); data = ((const char *)data + 64); len -= 64; } if (len) memcpy(ctx->W, data, len); + + return 0; } -void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) +int git_hash_final(git_oid *out, git_hash_ctx *ctx) { static const unsigned char pad[64] = { 0x80 }; unsigned int padlen[2]; @@ -271,10 +276,13 @@ void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx) padlen[1] = htonl((uint32_t)(ctx->size << 3)); i = ctx->size & 63; - git__blk_SHA1_Update(ctx, pad, 1+ (63 & (55 - i))); - git__blk_SHA1_Update(ctx, padlen, 8); + git_hash_update(ctx, pad, 1+ (63 & (55 - i))); + git_hash_update(ctx, padlen, 8); /* Output hash */ for (i = 0; i < 5; i++) - put_be32(hashout + i*4, ctx->H[i]); + put_be32(out->id + i*4, ctx->H[i]); + + return 0; } + diff --git a/src/hash/hash_generic.h b/src/hash/hash_generic.h new file mode 100644 index 000000000..7c4be7873 --- /dev/null +++ b/src/hash/hash_generic.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_generic_h__ +#define INCLUDE_hash_generic_h__ + +#include "hash.h" + +struct git_hash_ctx { + unsigned long long size; + unsigned int H[5]; + unsigned int W[16]; +}; + +#define git_hash_global_init() 0 +#define git_hash_global_shutdown() /* noop */ +#define git_hash_ctx_init(ctx) git_hash_init(ctx) +#define git_hash_ctx_cleanup(ctx) + +#endif /* INCLUDE_hash_generic_h__ */ diff --git a/src/hash/hash_openssl.h b/src/hash/hash_openssl.h new file mode 100644 index 000000000..3ae49a732 --- /dev/null +++ b/src/hash/hash_openssl.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_openssl_h__ +#define INCLUDE_hash_openssl_h__ + +#include "hash.h" + +#include <openssl/sha.h> + +struct git_hash_ctx { + SHA_CTX c; +}; + +#define git_hash_global_init() 0 +#define git_hash_global_shutdown() /* noop */ +#define git_hash_ctx_init(ctx) git_hash_init(ctx) +#define git_hash_ctx_cleanup(ctx) + +GIT_INLINE(int) git_hash_init(git_hash_ctx *ctx) +{ + assert(ctx); + SHA1_Init(&ctx->c); + return 0; +} + +GIT_INLINE(int) git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + assert(ctx); + SHA1_Update(&ctx->c, data, len); + return 0; +} + +GIT_INLINE(int) git_hash_final(git_oid *out, git_hash_ctx *ctx) +{ + assert(ctx); + SHA1_Final(out->id, &ctx->c); + return 0; +} + +#endif /* INCLUDE_hash_openssl_h__ */ diff --git a/src/ppc/sha1.c b/src/hash/hash_ppc.c index 803b81d0a..de89e9f5e 100644 --- a/src/ppc/sha1.c +++ b/src/hash/hash_ppc.c @@ -4,14 +4,17 @@ * 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 <stdio.h> #include <string.h> -#include "sha1.h" -extern void ppc_sha1_core(uint32_t *hash, const unsigned char *p, +#include "common.h" +#include "hash.h" + +extern void hash_ppc_core(uint32_t *hash, const unsigned char *p, unsigned int nblocks); -int ppc_SHA1_Init(ppc_SHA_CTX *c) +int git_hash_init(git_hash_ctx *c) { c->hash[0] = 0x67452301; c->hash[1] = 0xEFCDAB89; @@ -23,7 +26,7 @@ int ppc_SHA1_Init(ppc_SHA_CTX *c) return 0; } -int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n) +int git_hash_update(git_hash_ctx *c, const void *ptr, size_t n) { unsigned long nb; const unsigned char *p = ptr; @@ -36,12 +39,12 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n) nb = n; memcpy(&c->buf.b[c->cnt], p, nb); if ((c->cnt += nb) == 64) { - ppc_sha1_core(c->hash, c->buf.b, 1); + hash_ppc_core(c->hash, c->buf.b, 1); c->cnt = 0; } } else { nb = n >> 6; - ppc_sha1_core(c->hash, p, nb); + hash_ppc_core(c->hash, p, nb); nb <<= 6; } n -= nb; @@ -50,7 +53,7 @@ int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *ptr, unsigned long n) return 0; } -int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c) +int git_hash_final(git_oid *oid, git_hash_ctx *c) { unsigned int cnt = c->cnt; @@ -58,13 +61,14 @@ int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c) if (cnt > 56) { if (cnt < 64) memset(&c->buf.b[cnt], 0, 64 - cnt); - ppc_sha1_core(c->hash, c->buf.b, 1); + hash_ppc_core(c->hash, c->buf.b, 1); cnt = 0; } if (cnt < 56) memset(&c->buf.b[cnt], 0, 56 - cnt); c->buf.l[7] = c->len; - ppc_sha1_core(c->hash, c->buf.b, 1); - memcpy(hash, c->hash, 20); + hash_ppc_core(c->hash, c->buf.b, 1); + memcpy(oid->id, c->hash, 20); return 0; } + diff --git a/src/ppc/sha1.h b/src/hash/hash_ppc.h index aca4e5dda..935f73f7f 100644 --- a/src/ppc/sha1.h +++ b/src/hash/hash_ppc.h @@ -4,9 +4,13 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ + +#ifndef INCLUDE_hash_ppc_h__ +#define INCLUDE_hash_ppc_h__ + #include <stdint.h> -typedef struct { +struct git_hash_ctx { uint32_t hash[5]; uint32_t cnt; uint64_t len; @@ -14,13 +18,11 @@ typedef struct { unsigned char b[64]; uint64_t l[8]; } buf; -} ppc_SHA_CTX; +}; -int ppc_SHA1_Init(ppc_SHA_CTX *c); -int ppc_SHA1_Update(ppc_SHA_CTX *c, const void *p, unsigned long n); -int ppc_SHA1_Final(unsigned char *hash, ppc_SHA_CTX *c); +#define git_hash_global_init() 0 +#define git_hash_global_shutdown() /* noop */ +#define git_hash_ctx_init(ctx) git_hash_init(ctx) +#define git_hash_ctx_cleanup(ctx) -#define SHA_CTX ppc_SHA_CTX -#define SHA1_Init ppc_SHA1_Init -#define SHA1_Update ppc_SHA1_Update -#define SHA1_Final ppc_SHA1_Final +#endif /* INCLUDE_hash_generic_h__ */ diff --git a/src/ppc/sha1ppc.S b/src/hash/hash_ppc_core.S index 1711eef6e..1de816cf5 100644 --- a/src/ppc/sha1ppc.S +++ b/src/hash/hash_ppc_core.S @@ -162,8 +162,8 @@ add RE(t),RE(t),%r0; rotlwi RB(t),RB(t),30 STEPUP4(fn, (t)+12, (s)+12,); \ STEPUP4(fn, (t)+16, (s)+16, loadk) - .globl ppc_sha1_core -ppc_sha1_core: + .globl hash_ppc_core +hash_ppc_core: stwu %r1,-80(%r1) stmw %r13,4(%r1) diff --git a/src/hash/hash_win32.c b/src/hash/hash_win32.c new file mode 100644 index 000000000..a89dffa7c --- /dev/null +++ b/src/hash/hash_win32.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "global.h" +#include "hash.h" +#include "hash/hash_win32.h" + +#include <wincrypt.h> +#include <strsafe.h> + +static struct git_hash_prov hash_prov = {0}; + +/* Hash initialization */ + +/* Initialize CNG, if available */ +GIT_INLINE(int) hash_cng_prov_init(void) +{ + OSVERSIONINFOEX version_test = {0}; + DWORD version_test_mask; + DWORDLONG version_condition_mask = 0; + char dll_path[MAX_PATH]; + DWORD dll_path_len, size_len; + + return -1; + + /* Only use CNG on Windows 2008 / Vista SP1 or better (Windows 6.0 SP1) */ + version_test.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + version_test.dwMajorVersion = 6; + version_test.dwMinorVersion = 0; + version_test.wServicePackMajor = 1; + version_test.wServicePackMinor = 0; + + version_test_mask = (VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR); + + VER_SET_CONDITION(version_condition_mask, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(version_condition_mask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + if (!VerifyVersionInfo(&version_test, version_test_mask, version_condition_mask)) + return -1; + + /* Load bcrypt.dll explicitly from the system directory */ + if ((dll_path_len = GetSystemDirectory(dll_path, MAX_PATH)) == 0 || dll_path_len > MAX_PATH || + StringCchCat(dll_path, MAX_PATH, "\\") < 0 || + StringCchCat(dll_path, MAX_PATH, GIT_HASH_CNG_DLL_NAME) < 0 || + (hash_prov.prov.cng.dll = LoadLibrary(dll_path)) == NULL) + return -1; + + /* Load the function addresses */ + if ((hash_prov.prov.cng.open_algorithm_provider = (hash_win32_cng_open_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptOpenAlgorithmProvider")) == NULL || + (hash_prov.prov.cng.get_property = (hash_win32_cng_get_property_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptGetProperty")) == NULL || + (hash_prov.prov.cng.create_hash = (hash_win32_cng_create_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCreateHash")) == NULL || + (hash_prov.prov.cng.finish_hash = (hash_win32_cng_finish_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptFinishHash")) == NULL || + (hash_prov.prov.cng.hash_data = (hash_win32_cng_hash_data_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptHashData")) == NULL || + (hash_prov.prov.cng.destroy_hash = (hash_win32_cng_destroy_hash_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptDestroyHash")) == NULL || + (hash_prov.prov.cng.close_algorithm_provider = (hash_win32_cng_close_algorithm_provider_fn)GetProcAddress(hash_prov.prov.cng.dll, "BCryptCloseAlgorithmProvider")) == NULL) { + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + /* Load the SHA1 algorithm */ + if (hash_prov.prov.cng.open_algorithm_provider(&hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_TYPE, NULL, GIT_HASH_CNG_HASH_REUSABLE) < 0) { + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + /* Get storage space for the hash object */ + if (hash_prov.prov.cng.get_property(hash_prov.prov.cng.handle, GIT_HASH_CNG_HASH_OBJECT_LEN, (PBYTE)&hash_prov.prov.cng.hash_object_size, sizeof(DWORD), &size_len, 0) < 0) { + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + return -1; + } + + hash_prov.type = CNG; + return 0; +} + +GIT_INLINE(void) hash_cng_prov_shutdown(void) +{ + hash_prov.prov.cng.close_algorithm_provider(hash_prov.prov.cng.handle, 0); + FreeLibrary(hash_prov.prov.cng.dll); + + hash_prov.type = INVALID; +} + +/* Initialize CryptoAPI */ +GIT_INLINE(int) hash_cryptoapi_prov_init() +{ + if (!CryptAcquireContext(&hash_prov.prov.cryptoapi.handle, NULL, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) + return -1; + + hash_prov.type = CRYPTOAPI; + return 0; +} + +GIT_INLINE(void) hash_cryptoapi_prov_shutdown(void) +{ + CryptReleaseContext(hash_prov.prov.cryptoapi.handle, 0); + + hash_prov.type = INVALID; +} + +int git_hash_global_init() +{ + int error = 0; + + if (hash_prov.type != INVALID) + return 0; + + if ((error = hash_cng_prov_init()) < 0) + error = hash_cryptoapi_prov_init(); + + return error; +} + +void git_hash_global_shutdown() +{ + if (hash_prov.type == CNG) + hash_cng_prov_shutdown(); + else if(hash_prov.type == CRYPTOAPI) + hash_cryptoapi_prov_shutdown(); +} + +/* CryptoAPI: available in Windows XP and newer */ + +GIT_INLINE(int) hash_ctx_cryptoapi_init(git_hash_ctx *ctx) +{ + ctx->type = CRYPTOAPI; + ctx->prov = &hash_prov; + + return git_hash_init(ctx); +} + +GIT_INLINE(int) hash_cryptoapi_init(git_hash_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + + if (!CryptCreateHash(ctx->prov->prov.cryptoapi.handle, CALG_SHA1, 0, 0, &ctx->ctx.cryptoapi.hash_handle)) { + ctx->ctx.cryptoapi.valid = 0; + return -1; + } + + ctx->ctx.cryptoapi.valid = 1; + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + assert(ctx->ctx.cryptoapi.valid); + + if (!CryptHashData(ctx->ctx.cryptoapi.hash_handle, (const BYTE *)data, len, 0)) + return -1; + + return 0; +} + +GIT_INLINE(int) hash_cryptoapi_final(git_oid *out, git_hash_ctx *ctx) +{ + DWORD len = 20; + int error = 0; + + assert(ctx->ctx.cryptoapi.valid); + + if (!CryptGetHashParam(ctx->ctx.cryptoapi.hash_handle, HP_HASHVAL, out->id, &len, 0)) + error = -1; + + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); + ctx->ctx.cryptoapi.valid = 0; + + return error; +} + +GIT_INLINE(void) hash_ctx_cryptoapi_cleanup(git_hash_ctx *ctx) +{ + if (ctx->ctx.cryptoapi.valid) + CryptDestroyHash(ctx->ctx.cryptoapi.hash_handle); +} + +/* CNG: Available in Windows Server 2008 and newer */ + +GIT_INLINE(int) hash_ctx_cng_init(git_hash_ctx *ctx) +{ + if ((ctx->ctx.cng.hash_object = git__malloc(hash_prov.prov.cng.hash_object_size)) == NULL) + return -1; + + if (hash_prov.prov.cng.create_hash(hash_prov.prov.cng.handle, &ctx->ctx.cng.hash_handle, ctx->ctx.cng.hash_object, hash_prov.prov.cng.hash_object_size, NULL, 0, 0) < 0) { + git__free(ctx->ctx.cng.hash_object); + return -1; + } + + ctx->type = CNG; + ctx->prov = &hash_prov; + + return 0; +} + +GIT_INLINE(int) hash_cng_init(git_hash_ctx *ctx) +{ + BYTE hash[GIT_OID_RAWSZ]; + + if (!ctx->ctx.cng.updated) + return 0; + + /* CNG needs to be finished to restart */ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, hash, GIT_OID_RAWSZ, 0) < 0) + return -1; + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(int) hash_cng_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + if (ctx->prov->prov.cng.hash_data(ctx->ctx.cng.hash_handle, (PBYTE)data, len, 0) < 0) + return -1; + + return 0; +} + +GIT_INLINE(int) hash_cng_final(git_oid *out, git_hash_ctx *ctx) +{ + if (ctx->prov->prov.cng.finish_hash(ctx->ctx.cng.hash_handle, out->id, GIT_OID_RAWSZ, 0) < 0) + return -1; + + ctx->ctx.cng.updated = 0; + + return 0; +} + +GIT_INLINE(void) hash_ctx_cng_cleanup(git_hash_ctx *ctx) +{ + ctx->prov->prov.cng.destroy_hash(ctx->ctx.cng.hash_handle); + git__free(ctx->ctx.cng.hash_object); +} + +/* Indirection between CryptoAPI and CNG */ + +int git_hash_ctx_init(git_hash_ctx *ctx) +{ + int error = 0; + + assert(ctx); + + /* + * When compiled with GIT_THREADS, the global hash_prov data is + * initialized with git_threads_init. Otherwise, it must be initialized + * at first use. + */ + if (hash_prov.type == INVALID && (error = git_hash_global_init()) < 0) + return error; + + memset(ctx, 0x0, sizeof(git_hash_ctx)); + + return (hash_prov.type == CNG) ? hash_ctx_cng_init(ctx) : hash_ctx_cryptoapi_init(ctx); +} + +int git_hash_init(git_hash_ctx *ctx) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_init(ctx) : hash_cryptoapi_init(ctx); +} + +int git_hash_update(git_hash_ctx *ctx, const void *data, size_t len) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_update(ctx, data, len) : hash_cryptoapi_update(ctx, data, len); +} + +int git_hash_final(git_oid *out, git_hash_ctx *ctx) +{ + assert(ctx && ctx->type); + return (ctx->type == CNG) ? hash_cng_final(out, ctx) : hash_cryptoapi_final(out, ctx); +} + +void git_hash_ctx_cleanup(git_hash_ctx *ctx) +{ + assert(ctx); + + if (ctx->type == CNG) + hash_ctx_cng_cleanup(ctx); + else if(ctx->type == CRYPTOAPI) + hash_ctx_cryptoapi_cleanup(ctx); +} diff --git a/src/hash/hash_win32.h b/src/hash/hash_win32.h new file mode 100644 index 000000000..b91da3e37 --- /dev/null +++ b/src/hash/hash_win32.h @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_hash_win32_h__ +#define INCLUDE_hash_win32_h__ + +#include "common.h" +#include "hash.h" + +#include <wincrypt.h> +#include <strsafe.h> + +enum hash_win32_prov_type { + INVALID = 0, + CRYPTOAPI, + CNG +}; + +/* + * CryptoAPI is available for hashing on Windows XP and newer. + */ + +struct hash_cryptoapi_prov { + HCRYPTPROV handle; +}; + +/* + * CNG (bcrypt.dll) is significantly more performant than CryptoAPI and is + * preferred, however it is only available on Windows 2008 and newer and + * must therefore be dynamically loaded, and we must inline constants that + * would not exist when building in pre-Windows 2008 environments. + */ + +#define GIT_HASH_CNG_DLL_NAME "bcrypt.dll" + +/* BCRYPT_SHA1_ALGORITHM */ +#define GIT_HASH_CNG_HASH_TYPE L"SHA1" + +/* BCRYPT_OBJECT_LENGTH */ +#define GIT_HASH_CNG_HASH_OBJECT_LEN L"ObjectLength" + +/* BCRYPT_HASH_REUSEABLE_FLAGS */ +#define GIT_HASH_CNG_HASH_REUSABLE 0x00000020 + +/* Function declarations for CNG */ +typedef NTSTATUS (WINAPI *hash_win32_cng_open_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ *phAlgorithm, + LPCWSTR pszAlgId, + LPCWSTR pszImplementation, + DWORD dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_get_property_fn)( + HANDLE /* BCRYPT_HANDLE */ hObject, + LPCWSTR pszProperty, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG *pcbResult, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_create_hash_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + HANDLE /* BCRYPT_HASH_HANDLE */ *phHash, + PUCHAR pbHashObject, ULONG cbHashObject, + PUCHAR pbSecret, + ULONG cbSecret, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_finish_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbOutput, + ULONG cbOutput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_hash_data_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash, + PUCHAR pbInput, + ULONG cbInput, + ULONG dwFlags); + +typedef NTSTATUS (WINAPI *hash_win32_cng_destroy_hash_fn)( + HANDLE /* BCRYPT_HASH_HANDLE */ hHash); + +typedef NTSTATUS (WINAPI *hash_win32_cng_close_algorithm_provider_fn)( + HANDLE /* BCRYPT_ALG_HANDLE */ hAlgorithm, + ULONG dwFlags); + +struct hash_cng_prov { + /* DLL for CNG */ + HINSTANCE dll; + + /* Function pointers for CNG */ + hash_win32_cng_open_algorithm_provider_fn open_algorithm_provider; + hash_win32_cng_get_property_fn get_property; + hash_win32_cng_create_hash_fn create_hash; + hash_win32_cng_finish_hash_fn finish_hash; + hash_win32_cng_hash_data_fn hash_data; + hash_win32_cng_destroy_hash_fn destroy_hash; + hash_win32_cng_close_algorithm_provider_fn close_algorithm_provider; + + HANDLE /* BCRYPT_ALG_HANDLE */ handle; + DWORD hash_object_size; +}; + +struct git_hash_prov { + enum hash_win32_prov_type type; + + union { + struct hash_cryptoapi_prov cryptoapi; + struct hash_cng_prov cng; + } prov; +}; + +/* Hash contexts */ + +struct hash_cryptoapi_ctx { + bool valid; + HCRYPTHASH hash_handle; +}; + +struct hash_cng_ctx { + bool updated; + HANDLE /* BCRYPT_HASH_HANDLE */ hash_handle; + PBYTE hash_object; +}; + +struct git_hash_ctx { + enum hash_win32_prov_type type; + git_hash_prov *prov; + + union { + struct hash_cryptoapi_ctx cryptoapi; + struct hash_cng_ctx cng; + } ctx; +}; + +#endif /* INCLUDE_hash_openssl_h__ */ diff --git a/src/index.c b/src/index.c index f9f3b14cc..128dd18cf 100644 --- a/src/index.c +++ b/src/index.c @@ -13,6 +13,8 @@ #include "tree.h" #include "tree-cache.h" #include "hash.h" +#include "iterator.h" +#include "pathspec.h" #include "git2/odb.h" #include "git2/oid.h" #include "git2/blob.h" @@ -81,6 +83,11 @@ struct entry_long { char path[1]; /* arbitrary length */ }; +struct entry_srch_key { + const char *path; + int stage; +}; + /* local declarations */ static size_t read_extension(git_index *index, const char *buffer, size_t buffer_size); static size_t read_entry(git_index_entry *dest, const void *buffer, size_t buffer_size); @@ -90,53 +97,126 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) static int is_index_extended(git_index *index); static int write_index(git_index *index, git_filebuf *file); +static int index_find(git_index *index, const char *path, int stage); + static void index_entry_free(git_index_entry *entry); +static void index_entry_reuc_free(git_index_reuc_entry *reuc); + +GIT_INLINE(int) index_entry_stage(const git_index_entry *entry) +{ + return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; +} static int index_srch(const void *key, const void *array_member) { + const struct entry_srch_key *srch_key = key; const git_index_entry *entry = array_member; + int ret; + + ret = strcmp(srch_key->path, entry->path); - return strcmp(key, entry->path); + if (ret == 0) + ret = srch_key->stage - index_entry_stage(entry); + + return ret; } static int index_isrch(const void *key, const void *array_member) { + const struct entry_srch_key *srch_key = key; const git_index_entry *entry = array_member; + int ret; - return strcasecmp(key, entry->path); + ret = strcasecmp(srch_key->path, entry->path); + + if (ret == 0) + ret = srch_key->stage - index_entry_stage(entry); + + return ret; +} + +static int index_cmp_path(const void *a, const void *b) +{ + return strcmp((const char *)a, (const char *)b); +} + +static int index_icmp_path(const void *a, const void *b) +{ + return strcasecmp((const char *)a, (const char *)b); +} + +static int index_srch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcmp((const char *)path, entry->path); +} + +static int index_isrch_path(const void *path, const void *array_member) +{ + const git_index_entry *entry = array_member; + + return strcasecmp((const char *)path, entry->path); } static int index_cmp(const void *a, const void *b) { + int diff; const git_index_entry *entry_a = a; const git_index_entry *entry_b = b; - return strcmp(entry_a->path, entry_b->path); + diff = strcmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b)); + + return diff; } static int index_icmp(const void *a, const void *b) { + int diff; const git_index_entry *entry_a = a; const git_index_entry *entry_b = b; - return strcasecmp(entry_a->path, entry_b->path); + diff = strcasecmp(entry_a->path, entry_b->path); + + if (diff == 0) + diff = (index_entry_stage(entry_a) - index_entry_stage(entry_b)); + + return diff; +} + +static int reuc_srch(const void *key, const void *array_member) +{ + const git_index_reuc_entry *reuc = array_member; + + return strcmp(key, reuc->path); } -static int unmerged_srch(const void *key, const void *array_member) +static int reuc_isrch(const void *key, const void *array_member) { - const git_index_entry_unmerged *entry = array_member; + const git_index_reuc_entry *reuc = array_member; - return strcmp(key, entry->path); + return strcasecmp(key, reuc->path); } -static int unmerged_cmp(const void *a, const void *b) +static int reuc_cmp(const void *a, const void *b) { - const git_index_entry_unmerged *info_a = a; - const git_index_entry_unmerged *info_b = b; + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; return strcmp(info_a->path, info_b->path); } +static int reuc_icmp(const void *a, const void *b) +{ + const git_index_reuc_entry *info_a = a; + const git_index_reuc_entry *info_b = b; + + return strcasecmp(info_a->path, info_b->path); +} + static unsigned int index_create_mode(unsigned int mode) { if (S_ISLNK(mode)) @@ -165,40 +245,59 @@ static unsigned int index_merge_mode( static void index_set_ignore_case(git_index *index, bool ignore_case) { index->entries._cmp = ignore_case ? index_icmp : index_cmp; + index->entries_cmp_path = ignore_case ? index_icmp_path : index_cmp_path; index->entries_search = ignore_case ? index_isrch : index_srch; + index->entries_search_path = ignore_case ? index_isrch_path : index_srch_path; index->entries.sorted = 0; git_vector_sort(&index->entries); + + index->reuc._cmp = ignore_case ? reuc_icmp : reuc_cmp; + index->reuc_search = ignore_case ? reuc_isrch : reuc_srch; + index->reuc.sorted = 0; + git_vector_sort(&index->reuc); } int git_index_open(git_index **index_out, const char *index_path) { git_index *index; - assert(index_out && index_path); + assert(index_out); index = git__calloc(1, sizeof(git_index)); GITERR_CHECK_ALLOC(index); - index->index_file_path = git__strdup(index_path); - GITERR_CHECK_ALLOC(index->index_file_path); + if (index_path != NULL) { + index->index_file_path = git__strdup(index_path); + GITERR_CHECK_ALLOC(index->index_file_path); + + /* Check if index file is stored on disk already */ + if (git_path_exists(index->index_file_path) == true) + index->on_disk = 1; + } if (git_vector_init(&index->entries, 32, index_cmp) < 0) return -1; + index->entries_cmp_path = index_cmp_path; index->entries_search = index_srch; - - /* Check if index file is stored on disk already */ - if (git_path_exists(index->index_file_path) == true) - index->on_disk = 1; + index->entries_search_path = index_srch_path; + index->reuc_search = reuc_srch; *index_out = index; GIT_REFCOUNT_INC(index); - return git_index_read(index); + + return (index_path != NULL) ? git_index_read(index) : 0; +} + +int git_index_new(git_index **out) +{ + return git_index_open(out, NULL); } static void index_free(git_index *index) { git_index_entry *e; + git_index_reuc_entry *reuc; unsigned int i; git_index_clear(index); @@ -206,10 +305,10 @@ static void index_free(git_index *index) index_entry_free(e); } git_vector_free(&index->entries); - git_vector_foreach(&index->unmerged, i, e) { - index_entry_free(e); + git_vector_foreach(&index->reuc, i, reuc) { + index_entry_reuc_free(reuc); } - git_vector_free(&index->unmerged); + git_vector_free(&index->reuc); git__free(index->index_file_path); git__free(index); @@ -236,21 +335,27 @@ void git_index_clear(git_index *index) git__free(e); } - for (i = 0; i < index->unmerged.length; ++i) { - git_index_entry_unmerged *e; - e = git_vector_get(&index->unmerged, i); + for (i = 0; i < index->reuc.length; ++i) { + git_index_reuc_entry *e; + e = git_vector_get(&index->reuc, i); git__free(e->path); git__free(e); } git_vector_clear(&index->entries); - git_vector_clear(&index->unmerged); - index->last_modified = 0; + git_vector_clear(&index->reuc); + git_futils_filestamp_set(&index->stamp, NULL); git_tree_cache_free(index->tree); index->tree = NULL; } +static int create_index_error(int error, const char *msg) +{ + giterr_set(GITERR_INDEX, msg); + return error; +} + int git_index_set_caps(git_index *index, unsigned int caps) { int old_ignore_case; @@ -265,11 +370,8 @@ int git_index_set_caps(git_index *index, unsigned int caps) if (INDEX_OWNER(index) == NULL || git_repository_config__weakptr(&cfg, INDEX_OWNER(index)) < 0) - { - giterr_set(GITERR_INDEX, - "Cannot get repository config to set index caps"); - return -1; - } + return create_index_error(-1, + "Cannot get repository config to set index caps"); if (git_config_get_bool(&val, cfg, "core.ignorecase") == 0) index->ignore_case = (val != 0); @@ -301,11 +403,13 @@ unsigned int git_index_caps(const git_index *index) int git_index_read(git_index *index) { - int error, updated; + int error = 0, updated; git_buf buffer = GIT_BUF_INIT; - time_t mtime; + git_futils_filestamp stamp = {0}; - assert(index->index_file_path); + if (!index->index_file_path) + return create_index_error(-1, + "Failed to read index: The index is in-memory only"); if (!index->on_disk || git_path_exists(index->index_file_path) == false) { git_index_clear(index); @@ -313,33 +417,35 @@ int git_index_read(git_index *index) return 0; } - /* We don't want to update the mtime if we fail to parse the index */ - mtime = index->last_modified; - error = git_futils_readbuffer_updated( - &buffer, index->index_file_path, &mtime, &updated); + updated = git_futils_filestamp_check(&stamp, index->index_file_path); + if (updated <= 0) + return updated; + + error = git_futils_readbuffer(&buffer, index->index_file_path); if (error < 0) return error; - if (updated) { - git_index_clear(index); - error = parse_index(index, buffer.ptr, buffer.size); - - if (!error) - index->last_modified = mtime; + git_index_clear(index); + error = parse_index(index, buffer.ptr, buffer.size); - git_buf_free(&buffer); - } + if (!error) + git_futils_filestamp_set(&index->stamp, &stamp); + git_buf_free(&buffer); return error; } int git_index_write(git_index *index) { git_filebuf file = GIT_FILEBUF_INIT; - struct stat indexst; int error; + if (!index->index_file_path) + return create_index_error(-1, + "Failed to read index: The index is in-memory only"); + git_vector_sort(&index->entries); + git_vector_sort(&index->reuc); if ((error = git_filebuf_open( &file, index->index_file_path, GIT_FILEBUF_HASH_CONTENTS)) < 0) @@ -353,33 +459,63 @@ int git_index_write(git_index *index) if ((error = git_filebuf_commit(&file, GIT_INDEX_FILE_MODE)) < 0) return error; - if (p_stat(index->index_file_path, &indexst) == 0) { - index->last_modified = indexst.st_mtime; - index->on_disk = 1; - } + error = git_futils_filestamp_check(&index->stamp, index->index_file_path); + if (error < 0) + return error; + index->on_disk = 1; return 0; } +int git_index_write_tree(git_oid *oid, git_index *index) +{ + git_repository *repo; + + assert(oid && index); + + repo = INDEX_OWNER(index); + + if (repo == NULL) + return create_index_error(-1, "Failed to write tree. " + "The index file is not backed up by an existing repository"); + + return git_tree__write_index(oid, index, repo); +} + +int git_index_write_tree_to(git_oid *oid, git_index *index, git_repository *repo) +{ + assert(oid && index && repo); + return git_tree__write_index(oid, index, repo); +} + unsigned int git_index_entrycount(git_index *index) { assert(index); return (unsigned int)index->entries.length; } -unsigned int git_index_entrycount_unmerged(git_index *index) +git_index_entry *git_index_get_byindex(git_index *index, size_t n) { assert(index); - return (unsigned int)index->unmerged.length; + git_vector_sort(&index->entries); + return git_vector_get(&index->entries, n); } -git_index_entry *git_index_get(git_index *index, size_t n) +git_index_entry *git_index_get_bypath(git_index *index, const char *path, int stage) { + int pos; + + assert(index); + git_vector_sort(&index->entries); - return git_vector_get(&index->entries, n); + + if((pos = index_find(index, path, stage)) < 0) + return NULL; + + return git_index_get_byindex(index, pos); } -void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry) +void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st) { entry->ctime.seconds = (git_time_t)st->st_ctime; entry->mtime.seconds = (git_time_t)st->st_mtime; @@ -393,7 +529,23 @@ void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry) entry->file_size = st->st_size; } -static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path, int stage) +int git_index_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_index_entry__cmp_icase(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); +} + +static int index_entry_init(git_index_entry **entry_out, git_index *index, const char *rel_path) { git_index_entry *entry = NULL; struct stat st; @@ -402,15 +554,16 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const git_buf full_path = GIT_BUF_INIT; int error; - assert(stage >= 0 && stage <= 3); + if (INDEX_OWNER(index) == NULL) + return create_index_error(-1, + "Could not initialize index entry. " + "Index is not backed up by an existing repository."); - if (INDEX_OWNER(index) == NULL || - (workdir = git_repository_workdir(INDEX_OWNER(index))) == NULL) - { - giterr_set(GITERR_INDEX, + workdir = git_repository_workdir(INDEX_OWNER(index)); + + if (!workdir) + return create_index_error(GIT_EBAREREPO, "Could not initialize index entry. Repository is bare"); - return -1; - } if ((error = git_buf_joinpath(&full_path, workdir, rel_path)) < 0) return error; @@ -433,10 +586,9 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const entry = git__calloc(1, sizeof(git_index_entry)); GITERR_CHECK_ALLOC(entry); - git_index__init_entry_from_stat(&st, entry); + git_index_entry__init_from_stat(entry, &st); entry->oid = oid; - entry->flags |= (stage << GIT_IDXENTRY_STAGESHIFT); entry->path = git__strdup(rel_path); GITERR_CHECK_ALLOC(entry->path); @@ -444,6 +596,46 @@ static int index_entry_init(git_index_entry **entry_out, git_index *index, const return 0; } +static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, + const char *path, + int ancestor_mode, git_oid *ancestor_oid, + int our_mode, git_oid *our_oid, int their_mode, git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + + assert(reuc_out && path); + + *reuc_out = NULL; + + reuc = git__calloc(1, sizeof(git_index_reuc_entry)); + GITERR_CHECK_ALLOC(reuc); + + reuc->path = git__strdup(path); + if (reuc->path == NULL) + return -1; + + reuc->mode[0] = ancestor_mode; + git_oid_cpy(&reuc->oid[0], ancestor_oid); + + reuc->mode[1] = our_mode; + git_oid_cpy(&reuc->oid[1], our_oid); + + reuc->mode[2] = their_mode; + git_oid_cpy(&reuc->oid[2], their_oid); + + *reuc_out = reuc; + return 0; +} + +static void index_entry_reuc_free(git_index_reuc_entry *reuc) +{ + if (!reuc) + return; + + git__free(reuc->path); + git__free(reuc); +} + static git_index_entry *index_entry_dup(const git_index_entry *source_entry) { git_index_entry *entry; @@ -486,10 +678,10 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) if (path_length < GIT_IDXENTRY_NAMEMASK) entry->flags |= path_length & GIT_IDXENTRY_NAMEMASK; else - entry->flags |= GIT_IDXENTRY_NAMEMASK;; + entry->flags |= GIT_IDXENTRY_NAMEMASK; /* look if an entry with this path already exists */ - if ((position = git_index_find(index, entry->path)) >= 0) { + if ((position = index_find(index, entry->path, index_entry_stage(entry))) >= 0) { existing = (git_index_entry **)&index->entries.contents[position]; /* update filemode to existing values if stat is not trusted */ @@ -510,34 +702,56 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) return 0; } -static int index_add(git_index *index, const char *path, int stage, int replace) +static int index_conflict_to_reuc(git_index *index, const char *path) { - git_index_entry *entry = NULL; + git_index_entry *conflict_entries[3]; + int ancestor_mode, our_mode, their_mode; + git_oid *ancestor_oid, *our_oid, *their_oid; int ret; - if ((ret = index_entry_init(&entry, index, path, stage)) < 0 || - (ret = index_insert(index, entry, replace)) < 0) - { - index_entry_free(entry); + if ((ret = git_index_conflict_get(&conflict_entries[0], + &conflict_entries[1], &conflict_entries[2], index, path)) < 0) return ret; - } - git_tree_cache_invalidate_path(index->tree, entry->path); - return 0; -} + ancestor_mode = conflict_entries[0] == NULL ? 0 : conflict_entries[0]->mode; + our_mode = conflict_entries[1] == NULL ? 0 : conflict_entries[1]->mode; + their_mode = conflict_entries[2] == NULL ? 0 : conflict_entries[2]->mode; -int git_index_add(git_index *index, const char *path, int stage) -{ - return index_add(index, path, stage, 1); + ancestor_oid = conflict_entries[0] == NULL ? NULL : &conflict_entries[0]->oid; + our_oid = conflict_entries[1] == NULL ? NULL : &conflict_entries[1]->oid; + their_oid = conflict_entries[2] == NULL ? NULL : &conflict_entries[2]->oid; + + if ((ret = git_index_reuc_add(index, path, ancestor_mode, ancestor_oid, + our_mode, our_oid, their_mode, their_oid)) >= 0) + ret = git_index_conflict_remove(index, path); + + return ret; } -int git_index_append(git_index *index, const char *path, int stage) +int git_index_add_from_workdir(git_index *index, const char *path) { - return index_add(index, path, stage, 0); + git_index_entry *entry = NULL; + int ret; + + assert(index && path); + + if ((ret = index_entry_init(&entry, index, path)) < 0 || + (ret = index_insert(index, entry, 1)) < 0) + goto on_error; + + /* Adding implies conflict was resolved, move conflict entries to REUC */ + if ((ret = index_conflict_to_reuc(index, path)) < 0 && ret != GIT_ENOTFOUND) + goto on_error; + + git_tree_cache_invalidate_path(index->tree, entry->path); + return 0; + +on_error: + index_entry_free(entry); + return ret; } -static int index_add2( - git_index *index, const git_index_entry *source_entry, int replace) +int git_index_add(git_index *index, const git_index_entry *source_entry) { git_index_entry *entry = NULL; int ret; @@ -546,7 +760,7 @@ static int index_add2( if (entry == NULL) return -1; - if ((ret = index_insert(index, entry, replace)) < 0) { + if ((ret = index_insert(index, entry, 1)) < 0) { index_entry_free(entry); return ret; } @@ -555,23 +769,17 @@ static int index_add2( return 0; } -int git_index_add2(git_index *index, const git_index_entry *source_entry) -{ - return index_add2(index, source_entry, 1); -} - -int git_index_append2(git_index *index, const git_index_entry *source_entry) -{ - return index_add2(index, source_entry, 0); -} - -int git_index_remove(git_index *index, int position) +int git_index_remove(git_index *index, const char *path, int stage) { + int position; int error; git_index_entry *entry; git_vector_sort(&index->entries); + if ((position = index_find(index, path, stage)) < 0) + return position; + entry = git_vector_get(&index->entries, position); if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); @@ -584,45 +792,300 @@ int git_index_remove(git_index *index, int position) return error; } +static int index_find(git_index *index, const char *path, int stage) +{ + struct entry_srch_key srch_key; + + assert(path); + + srch_key.path = path; + srch_key.stage = stage; + + return git_vector_bsearch2(&index->entries, index->entries_search, &srch_key); +} + int git_index_find(git_index *index, const char *path) { - return git_vector_bsearch2(&index->entries, index->entries_search, path); + int pos; + + assert(index && path); + + if ((pos = git_vector_bsearch2(&index->entries, index->entries_search_path, path)) < 0) + return pos; + + /* Since our binary search only looked at path, we may be in the + * middle of a list of stages. */ + while (pos > 0) { + git_index_entry *prev = git_vector_get(&index->entries, pos-1); + + if (index->entries_cmp_path(prev->path, path) != 0) + break; + + --pos; + } + + return pos; } unsigned int git_index__prefix_position(git_index *index, const char *path) { + struct entry_srch_key srch_key; unsigned int pos; - git_vector_bsearch3(&pos, &index->entries, index->entries_search, path); + srch_key.path = path; + srch_key.stage = 0; + + git_vector_sort(&index->entries); + git_vector_bsearch3( + &pos, &index->entries, index->entries_search, &srch_key); return pos; } -void git_index_uniq(git_index *index) +int git_index_conflict_add(git_index *index, + const git_index_entry *ancestor_entry, + const git_index_entry *our_entry, + const git_index_entry *their_entry) { - git_vector_uniq(&index->entries); + git_index_entry *entries[3] = { 0 }; + size_t i; + int ret = 0; + + assert (index); + + if ((ancestor_entry != NULL && (entries[0] = index_entry_dup(ancestor_entry)) == NULL) || + (our_entry != NULL && (entries[1] = index_entry_dup(our_entry)) == NULL) || + (their_entry != NULL && (entries[2] = index_entry_dup(their_entry)) == NULL)) + return -1; + + for (i = 0; i < 3; i++) { + if (entries[i] == NULL) + continue; + + /* Make sure stage is correct */ + entries[i]->flags = (entries[i]->flags & ~GIT_IDXENTRY_STAGEMASK) | + ((i+1) << GIT_IDXENTRY_STAGESHIFT); + + if ((ret = index_insert(index, entries[i], 1)) < 0) + goto on_error; + } + + return 0; + +on_error: + for (i = 0; i < 3; i++) { + if (entries[i] != NULL) + index_entry_free(entries[i]); + } + + return ret; } -const git_index_entry_unmerged *git_index_get_unmerged_bypath( +int git_index_conflict_get(git_index_entry **ancestor_out, + git_index_entry **our_out, + git_index_entry **their_out, git_index *index, const char *path) { + int pos, stage; + git_index_entry *conflict_entry; + int error = GIT_ENOTFOUND; + + assert(ancestor_out && our_out && their_out && index && path); + + *ancestor_out = NULL; + *our_out = NULL; + *their_out = NULL; + + if ((pos = git_index_find(index, path)) < 0) + return pos; + + while ((unsigned int)pos < git_index_entrycount(index)) { + conflict_entry = git_vector_get(&index->entries, pos); + + if (index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + stage = index_entry_stage(conflict_entry); + + switch (stage) { + case 3: + *their_out = conflict_entry; + error = 0; + break; + case 2: + *our_out = conflict_entry; + error = 0; + break; + case 1: + *ancestor_out = conflict_entry; + error = 0; + break; + default: + break; + }; + + ++pos; + } + + return error; +} + +int git_index_conflict_remove(git_index *index, const char *path) +{ int pos; + git_index_entry *conflict_entry; + int error = 0; + assert(index && path); - if (!index->unmerged.length) + if ((pos = git_index_find(index, path)) < 0) + return pos; + + while ((unsigned int)pos < git_index_entrycount(index)) { + conflict_entry = git_vector_get(&index->entries, pos); + + if (index->entries_cmp_path(conflict_entry->path, path) != 0) + break; + + if (index_entry_stage(conflict_entry) == 0) { + pos++; + continue; + } + + error = git_vector_remove(&index->entries, (unsigned int)pos); + + if (error >= 0) + index_entry_free(conflict_entry); + } + + return error; +} + +static int index_conflicts_match(git_vector *v, size_t idx) +{ + git_index_entry *entry = git_vector_get(v, idx); + + if (index_entry_stage(entry) > 0) { + index_entry_free(entry); + return 1; + } + + return 0; +} + +void git_index_conflict_cleanup(git_index *index) +{ + assert(index); + git_vector_remove_matching(&index->entries, index_conflicts_match); +} + +int git_index_has_conflicts(git_index *index) +{ + unsigned int i; + git_index_entry *entry; + + assert(index); + + git_vector_foreach(&index->entries, i, entry) { + if (index_entry_stage(entry) > 0) + return 1; + } + + return 0; +} + +unsigned int git_index_reuc_entrycount(git_index *index) +{ + assert(index); + return (unsigned int)index->reuc.length; +} + +static int index_reuc_insert(git_index *index, git_index_reuc_entry *reuc, int replace) +{ + git_index_reuc_entry **existing = NULL; + int position; + + assert(index && reuc && reuc->path != NULL); + + if ((position = git_index_reuc_find(index, reuc->path)) >= 0) + existing = (git_index_reuc_entry **)&index->reuc.contents[position]; + + if (!replace || !existing) + return git_vector_insert(&index->reuc, reuc); + + /* exists, replace it */ + git__free((*existing)->path); + git__free(*existing); + *existing = reuc; + + return 0; +} + +int git_index_reuc_add(git_index *index, const char *path, + int ancestor_mode, git_oid *ancestor_oid, + int our_mode, git_oid *our_oid, + int their_mode, git_oid *their_oid) +{ + git_index_reuc_entry *reuc = NULL; + int error = 0; + + assert(index && path); + + if ((error = index_entry_reuc_init(&reuc, path, ancestor_mode, ancestor_oid, our_mode, our_oid, their_mode, their_oid)) < 0 || + (error = index_reuc_insert(index, reuc, 1)) < 0) + { + index_entry_reuc_free(reuc); + return error; + } + + return error; +} + +int git_index_reuc_find(git_index *index, const char *path) +{ + return git_vector_bsearch2(&index->reuc, index->reuc_search, path); +} + +const git_index_reuc_entry *git_index_reuc_get_bypath( + git_index *index, const char *path) +{ + int pos; + assert(index && path); + + if (!index->reuc.length) return NULL; - if ((pos = git_vector_bsearch2(&index->unmerged, unmerged_srch, path)) < 0) + git_vector_sort(&index->reuc); + + if ((pos = git_index_reuc_find(index, path)) < 0) return NULL; - return git_vector_get(&index->unmerged, pos); + return git_vector_get(&index->reuc, pos); } -const git_index_entry_unmerged *git_index_get_unmerged_byindex( +const git_index_reuc_entry *git_index_reuc_get_byindex( git_index *index, size_t n) { assert(index); - return git_vector_get(&index->unmerged, n); + + git_vector_sort(&index->reuc); + return git_vector_get(&index->reuc, n); +} + +int git_index_reuc_remove(git_index *index, int position) +{ + int error; + git_index_reuc_entry *reuc; + + git_vector_sort(&index->reuc); + + reuc = git_vector_get(&index->reuc, position); + error = git_vector_remove(&index->reuc, (unsigned int)position); + + if (!error) + index_entry_reuc_free(reuc); + + return error; } static int index_error_invalid(const char *message) @@ -631,26 +1094,27 @@ static int index_error_invalid(const char *message) return -1; } -static int read_unmerged(git_index *index, const char *buffer, size_t size) +static int read_reuc(git_index *index, const char *buffer, size_t size) { const char *endptr; size_t len; int i; - if (git_vector_init(&index->unmerged, 16, unmerged_cmp) < 0) + /* This gets called multiple times, the vector might already be initialized */ + if (index->reuc._alloc_size == 0 && git_vector_init(&index->reuc, 16, reuc_cmp) < 0) return -1; while (size) { - git_index_entry_unmerged *lost; + git_index_reuc_entry *lost; len = strlen(buffer) + 1; if (size <= len) - return index_error_invalid("reading unmerged entries"); + return index_error_invalid("reading reuc entries"); - lost = git__malloc(sizeof(git_index_entry_unmerged)); + lost = git__malloc(sizeof(git_index_reuc_entry)); GITERR_CHECK_ALLOC(lost); - if (git_vector_insert(&index->unmerged, lost) < 0) + if (git_vector_insert(&index->reuc, lost) < 0) return -1; /* read NUL-terminated pathname for entry */ @@ -667,13 +1131,13 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size) if (git__strtol32(&tmp, buffer, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || (unsigned)tmp > UINT_MAX) - return index_error_invalid("reading unmerged entry stage"); + return index_error_invalid("reading reuc entry stage"); lost->mode[i] = tmp; len = (endptr + 1) - buffer; if (size <= len) - return index_error_invalid("reading unmerged entry stage"); + return index_error_invalid("reading reuc entry stage"); size -= len; buffer += len; @@ -684,7 +1148,7 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size) if (!lost->mode[i]) continue; if (size < 20) - return index_error_invalid("reading unmerged entry oid"); + return index_error_invalid("reading reuc entry oid"); git_oid_fromraw(&lost->oid[i], (const unsigned char *) buffer); size -= 20; @@ -692,6 +1156,9 @@ static int read_unmerged(git_index *index, const char *buffer, size_t size) } } + /* entries are guaranteed to be sorted on-disk */ + index->reuc.sorted = 1; + return 0; } @@ -797,7 +1264,7 @@ static size_t read_extension(git_index *index, const char *buffer, size_t buffer if (git_tree_cache_read(&index->tree, buffer + 8, dest.extension_size) < 0) return 0; } else if (memcmp(dest.signature, INDEX_EXT_UNMERGED_SIG, 4) == 0) { - if (read_unmerged(index, buffer + 8, dest.extension_size) < 0) + if (read_reuc(index, buffer + 8, dest.extension_size) < 0) return 0; } /* else, unsupported extension. We cannot parse this, but we can skip @@ -996,6 +1463,69 @@ static int write_entries(git_index *index, git_filebuf *file) return error; } +static int write_extension(git_filebuf *file, struct index_extension *header, git_buf *data) +{ + struct index_extension ondisk; + int error = 0; + + memset(&ondisk, 0x0, sizeof(struct index_extension)); + memcpy(&ondisk, header, 4); + ondisk.extension_size = htonl(header->extension_size); + + if ((error = git_filebuf_write(file, &ondisk, sizeof(struct index_extension))) == 0) + error = git_filebuf_write(file, data->ptr, data->size); + + return error; +} + +static int create_reuc_extension_data(git_buf *reuc_buf, git_index_reuc_entry *reuc) +{ + int i; + int error = 0; + + if ((error = git_buf_put(reuc_buf, reuc->path, strlen(reuc->path) + 1)) < 0) + return error; + + for (i = 0; i < 3; i++) { + if ((error = git_buf_printf(reuc_buf, "%o", reuc->mode[i])) < 0 || + (error = git_buf_put(reuc_buf, "\0", 1)) < 0) + return error; + } + + for (i = 0; i < 3; i++) { + if (reuc->mode[i] && (error = git_buf_put(reuc_buf, (char *)&reuc->oid[i].id, GIT_OID_RAWSZ)) < 0) + return error; + } + + return 0; +} + +static int write_reuc_extension(git_index *index, git_filebuf *file) +{ + git_buf reuc_buf = GIT_BUF_INIT; + git_vector *out = &index->reuc; + git_index_reuc_entry *reuc; + struct index_extension extension; + unsigned int i; + int error = 0; + + git_vector_foreach(out, i, reuc) { + if ((error = create_reuc_extension_data(&reuc_buf, reuc)) < 0) + goto done; + } + + memset(&extension, 0x0, sizeof(struct index_extension)); + memcpy(&extension.signature, INDEX_EXT_UNMERGED_SIG, 4); + extension.extension_size = reuc_buf.size; + + error = write_extension(file, &extension, &reuc_buf); + + git_buf_free(&reuc_buf); + +done: + return error; +} + static int write_index(git_index *index, git_filebuf *file) { git_oid hash_final; @@ -1018,7 +1548,11 @@ static int write_index(git_index *index, git_filebuf *file) if (write_entries(index, file) < 0) return -1; - /* TODO: write extensions (tree cache) */ + /* TODO: write tree cache extension */ + + /* write the reuc extension */ + if (index->reuc.length > 0 && write_reuc_extension(index, file) < 0) + return -1; /* get out the hash for all the contents we've appended to the file */ git_filebuf_hash(&hash_final, file); @@ -1029,22 +1563,20 @@ static int write_index(git_index *index, git_filebuf *file) int git_index_entry_stage(const git_index_entry *entry) { - return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; + return index_entry_stage(entry); } typedef struct read_tree_data { git_index *index; - git_indexer_stats *stats; + git_transfer_progress *stats; } read_tree_data; static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data) { - read_tree_data *rtd = data; + git_index *index = (git_index *)data; git_index_entry *entry = NULL; git_buf path = GIT_BUF_INIT; - rtd->stats->total++; - if (git_tree_entry__is_tree(tentry)) return 0; @@ -1059,7 +1591,7 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da entry->path = git_buf_detach(&path); git_buf_free(&path); - if (index_insert(rtd->index, entry, 0) < 0) { + if (index_insert(index, entry, 0) < 0) { index_entry_free(entry); return -1; } @@ -1067,16 +1599,65 @@ static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *da return 0; } -int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats) +int git_index_read_tree(git_index *index, git_tree *tree) { - git_indexer_stats dummy_stats; - read_tree_data rtd = {index, NULL}; + git_index_clear(index); - if (!stats) stats = &dummy_stats; - stats->total = 0; - rtd.stats = stats; + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); +} - git_index_clear(index); +git_repository *git_index_owner(const git_index *index) +{ + return INDEX_OWNER(index); +} - return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd); +int git_index_read_tree_match( + git_index *index, git_tree *tree, git_strarray *strspec) +{ +#if 0 + git_iterator *iter = NULL; + const git_index_entry *entry; + char *pfx = NULL; + git_vector pathspec = GIT_VECTOR_INIT; + git_pool pathpool = GIT_POOL_INIT_STRINGPOOL; +#endif + + if (!git_pathspec_is_interesting(strspec)) + return git_index_read_tree(index, tree); + + return git_index_read_tree(index, tree); + +#if 0 + /* The following loads the matches into the index, but doesn't + * erase obsoleted entries (e.g. you load a blob at "a/b" which + * should obsolete a blob at "a/b/c/d" since b is no longer a tree) + */ + + if (git_pathspec_init(&pathspec, strspec, &pathpool) < 0) + return -1; + + pfx = git_pathspec_prefix(strspec); + + if ((error = git_iterator_for_tree_range( + &iter, INDEX_OWNER(index), tree, pfx, pfx)) < 0 || + (error = git_iterator_current(iter, &entry)) < 0) + goto cleanup; + + while (entry != NULL) { + if (git_pathspec_match_path(&pathspec, entry->path, false, false) && + (error = git_index_add(index, entry)) < 0) + goto cleanup; + + if ((error = git_iterator_advance(iter, &entry)) < 0) + goto cleanup; + } + +cleanup: + git_iterator_free(iter); + git_pathspec_free(&pathspec); + git_pool_clear(&pathpool); + git__free(pfx); + + return error; +#endif } diff --git a/src/index.h b/src/index.h index 7dd23ee60..f0dcd64d5 100644 --- a/src/index.h +++ b/src/index.h @@ -22,7 +22,7 @@ struct git_index { char *index_file_path; - time_t last_modified; + git_futils_filestamp stamp; git_vector entries; unsigned int on_disk:1; @@ -33,13 +33,22 @@ struct git_index { git_tree_cache *tree; - git_vector unmerged; + git_vector reuc; + git_vector_cmp entries_cmp_path; git_vector_cmp entries_search; + git_vector_cmp entries_search_path; + git_vector_cmp reuc_search; }; -extern void git_index__init_entry_from_stat(struct stat *st, git_index_entry *entry); +extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); extern unsigned int git_index__prefix_position(git_index *index, const char *path); +extern int git_index_entry__cmp(const void *a, const void *b); +extern int git_index_entry__cmp_icase(const void *a, const void *b); + +extern int git_index_read_tree_match( + git_index *index, git_tree *tree, git_strarray *strspec); + #endif diff --git a/src/indexer.c b/src/indexer.c index 7d4e18d7a..e9f235a72 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -17,7 +17,6 @@ #include "posix.h" #include "pack.h" #include "filebuf.h" -#include "sha1.h" #define UINT31_MAX (0x7FFFFFFF) @@ -49,6 +48,8 @@ struct git_indexer_stream { git_vector deltas; unsigned int fanout[256]; git_oid hash; + git_transfer_progress_callback progress_cb; + void *progress_payload; }; struct delta_info { @@ -138,7 +139,11 @@ static int cache_cmp(const void *a, const void *b) return git_oid_cmp(&ea->sha1, &eb->sha1); } -int git_indexer_stream_new(git_indexer_stream **out, const char *prefix) +int git_indexer_stream_new( + git_indexer_stream **out, + const char *prefix, + git_transfer_progress_callback progress_cb, + void *progress_payload) { git_indexer_stream *idx; git_buf path = GIT_BUF_INIT; @@ -147,6 +152,8 @@ int git_indexer_stream_new(git_indexer_stream **out, const char *prefix) idx = git__calloc(1, sizeof(git_indexer_stream)); GITERR_CHECK_ALLOC(idx); + idx->progress_cb = progress_cb; + idx->progress_payload = progress_payload; error = git_buf_joinpath(&path, prefix, suff); if (error < 0) @@ -242,8 +249,10 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent git_oid_cpy(&pentry->sha1, &oid); pentry->offset = entry_start; - if (git_vector_insert(&idx->pack->cache, pentry) < 0) + if (git_vector_insert(&idx->pack->cache, pentry) < 0) { + git__free(pentry); goto on_error; + } git_oid_cpy(&entry->oid, &oid); entry->crc = crc32(0L, Z_NULL, 0); @@ -268,12 +277,17 @@ static int hash_and_save(git_indexer_stream *idx, git_rawobj *obj, git_off_t ent on_error: git__free(entry); - git__free(pentry); git__free(obj->data); return -1; } -int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_indexer_stats *stats) +static void do_progress_callback(git_indexer_stream *idx, git_transfer_progress *stats) +{ + if (!idx->progress_cb) return; + idx->progress_cb(stats, idx->progress_payload); +} + +int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t size, git_transfer_progress *stats) { int error; struct git_pack_header hdr; @@ -282,7 +296,7 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz assert(idx && data && stats); - processed = stats->processed; + processed = stats->indexed_objects; if (git_filebuf_write(&idx->pack_file, data, size) < 0) return -1; @@ -324,8 +338,10 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz if (git_vector_init(&idx->deltas, (unsigned int)(idx->nr_objects / 2), NULL) < 0) return -1; - memset(stats, 0, sizeof(git_indexer_stats)); - stats->total = (unsigned int)idx->nr_objects; + stats->received_objects = 0; + stats->indexed_objects = 0; + stats->total_objects = (unsigned int)idx->nr_objects; + do_progress_callback(idx, stats); } /* Now that we have data in the pack, let's try to parse it */ @@ -361,7 +377,8 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz if (error < 0) return error; - stats->received++; + stats->received_objects++; + do_progress_callback(idx, stats); continue; } @@ -379,8 +396,9 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz git__free(obj.data); - stats->processed = (unsigned int)++processed; - stats->received++; + stats->indexed_objects = (unsigned int)++processed; + stats->received_objects++; + do_progress_callback(idx, stats); } return 0; @@ -412,7 +430,7 @@ static int index_path_stream(git_buf *path, git_indexer_stream *idx, const char return git_buf_oom(path) ? -1 : 0; } -static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats) +static int resolve_deltas(git_indexer_stream *idx, git_transfer_progress *stats) { unsigned int i; struct delta_info *delta; @@ -428,13 +446,14 @@ static int resolve_deltas(git_indexer_stream *idx, git_indexer_stats *stats) return -1; git__free(obj.data); - stats->processed++; + stats->indexed_objects++; + do_progress_callback(idx, stats); } return 0; } -int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stats) +int git_indexer_stream_finalize(git_indexer_stream *idx, git_transfer_progress *stats) { git_mwindow *w = NULL; unsigned int i, long_offsets = 0, left; @@ -443,7 +462,10 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat struct entry *entry; void *packfile_hash; git_oid file_hash; - SHA_CTX ctx; + git_hash_ctx ctx; + + if (git_hash_ctx_init(&ctx) < 0) + return -1; /* Test for this before resolve_deltas(), as it plays with idx->off */ if (idx->off < idx->pack->mwf.size - GIT_OID_RAWSZ) { @@ -455,7 +477,7 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat if (resolve_deltas(idx, stats) < 0) return -1; - if (stats->processed != stats->total) { + if (stats->indexed_objects != stats->total_objects) { giterr_set(GITERR_INDEXER, "Indexing error: early EOF"); return -1; } @@ -483,12 +505,11 @@ int git_indexer_stream_finalize(git_indexer_stream *idx, git_indexer_stats *stat } /* Write out the object names (SHA-1 hashes) */ - SHA1_Init(&ctx); git_vector_foreach(&idx->objects, i, entry) { git_filebuf_write(&idx->index_file, &entry->oid, sizeof(git_oid)); - SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); + git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ); } - SHA1_Final(idx->hash.id, &ctx); + git_hash_final(&idx->hash, &ctx); /* Write out the CRC32 values */ git_vector_foreach(&idx->objects, i, entry) { @@ -563,6 +584,7 @@ on_error: p_close(idx->pack->mwf.fd); git_filebuf_cleanup(&idx->index_file); git_buf_free(&filename); + git_hash_ctx_cleanup(&ctx); return -1; } @@ -663,7 +685,10 @@ int git_indexer_write(git_indexer *idx) struct entry *entry; void *packfile_hash; git_oid file_hash; - SHA_CTX ctx; + git_hash_ctx ctx; + + if (git_hash_ctx_init(&ctx) < 0) + return -1; git_vector_sort(&idx->objects); @@ -693,14 +718,14 @@ int git_indexer_write(git_indexer *idx) } /* Write out the object names (SHA-1 hashes) */ - SHA1_Init(&ctx); git_vector_foreach(&idx->objects, i, entry) { - error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid)); - SHA1_Update(&ctx, &entry->oid, GIT_OID_RAWSZ); - if (error < 0) + if ((error = git_filebuf_write(&idx->file, &entry->oid, sizeof(git_oid))) < 0 || + (error = git_hash_update(&ctx, &entry->oid, GIT_OID_RAWSZ)) < 0) goto cleanup; } - SHA1_Final(idx->hash.id, &ctx); + + if ((error = git_hash_final(&idx->hash, &ctx)) < 0) + goto cleanup; /* Write out the CRC32 values */ git_vector_foreach(&idx->objects, i, entry) { @@ -778,11 +803,12 @@ cleanup: if (error < 0) git_filebuf_cleanup(&idx->file); git_buf_free(&filename); + git_hash_ctx_cleanup(&ctx); return error; } -int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) +int git_indexer_run(git_indexer *idx, git_transfer_progress *stats) { git_mwindow_file *mwf; git_off_t off = sizeof(struct git_pack_header); @@ -797,8 +823,8 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) if (error < 0) return error; - stats->total = (unsigned int)idx->nr_objects; - stats->processed = processed = 0; + stats->total_objects = (unsigned int)idx->nr_objects; + stats->indexed_objects = processed = 0; while (processed < idx->nr_objects) { git_rawobj obj; @@ -868,7 +894,7 @@ int git_indexer_run(git_indexer *idx, git_indexer_stats *stats) git__free(obj.data); - stats->processed = ++processed; + stats->indexed_objects = ++processed; } cleanup: diff --git a/src/iterator.c b/src/iterator.c index df6da9a87..ee83a4fda 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -330,13 +330,14 @@ typedef struct { git_iterator base; git_index *index; unsigned int current; + bool free_index; } index_iterator; static int index_iterator__current( git_iterator *self, const git_index_entry **entry) { index_iterator *ii = (index_iterator *)self; - git_index_entry *ie = git_index_get(ii->index, ii->current); + git_index_entry *ie = git_index_get_byindex(ii->index, ii->current); if (ie != NULL && ii->base.end != NULL && @@ -387,32 +388,47 @@ static int index_iterator__reset(git_iterator *self) static void index_iterator__free(git_iterator *self) { index_iterator *ii = (index_iterator *)self; - git_index_free(ii->index); + if (ii->free_index) + git_index_free(ii->index); ii->index = NULL; } int git_iterator_for_index_range( git_iterator **iter, - git_repository *repo, + git_index *index, const char *start, const char *end) { - int error; index_iterator *ii; ITERATOR_BASE_INIT(ii, index, INDEX); - if ((error = git_repository_index(&ii->index, repo)) < 0) - git__free(ii); - else { - ii->base.ignore_case = ii->index->ignore_case; - ii->current = start ? git_index__prefix_position(ii->index, start) : 0; - *iter = (git_iterator *)ii; - } + ii->index = index; + ii->base.ignore_case = ii->index->ignore_case; + ii->current = start ? git_index__prefix_position(ii->index, start) : 0; - return error; + *iter = (git_iterator *)ii; + + return 0; } +int git_iterator_for_repo_index_range( + git_iterator **iter, + git_repository *repo, + const char *start, + const char *end) +{ + int error; + git_index *index; + + if ((error = git_repository_index(&index, repo)) < 0) + return error; + + if (!(error = git_iterator_for_index_range(iter, index, start, end))) + ((index_iterator *)(*iter))->free_index = true; + + return error; +} typedef struct workdir_iterator_frame workdir_iterator_frame; struct workdir_iterator_frame { @@ -641,26 +657,23 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) wi->entry.path = ps->path; - /* skip over .git entry */ + /* skip over .git entries */ if (STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT "/") == 0 || STRCMP_CASESELECT(wi->base.ignore_case, ps->path, DOT_GIT) == 0) return workdir_iterator__advance((git_iterator *)wi, NULL); - /* if there is an error processing the entry, treat as ignored */ - wi->is_ignored = 1; + wi->is_ignored = -1; - git_index__init_entry_from_stat(&ps->st, &wi->entry); + git_index_entry__init_from_stat(&wi->entry, &ps->st); /* need different mode here to keep directories during iteration */ wi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); /* if this is a file type we don't handle, treat as ignored */ - if (wi->entry.mode == 0) + if (wi->entry.mode == 0) { + wi->is_ignored = 1; return 0; - - /* okay, we are far enough along to look up real ignore rule */ - if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0) - return 0; /* if error, ignore it and ignore file */ + } /* detect submodules */ if (S_ISDIR(wi->entry.mode)) { @@ -693,24 +706,21 @@ int git_iterator_for_workdir_range( assert(iter && repo); - if ((error = git_repository__ensure_not_bare(repo, "scan working directory")) < 0) + if ((error = git_repository__ensure_not_bare( + repo, "scan working directory")) < 0) return error; ITERATOR_BASE_INIT(wi, workdir, WORKDIR); - wi->repo = repo; - if ((error = git_repository_index(&index, repo)) < 0) { + if ((error = git_repository_index__weakptr(&index, repo)) < 0) { git__free(wi); return error; } - /* Set the ignore_case flag for the workdir iterator to match - * that of the index. */ + /* Match ignore_case flag for iterator to that of the index */ wi->base.ignore_case = index->ignore_case; - git_index_free(index); - if (git_buf_sets(&wi->path, git_repository_workdir(repo)) < 0 || git_path_to_dir(&wi->path) < 0 || git_ignore__for_path(repo, "", &wi->ignores) < 0) @@ -908,8 +918,18 @@ notfound: int git_iterator_current_is_ignored(git_iterator *iter) { - return (iter->type != GIT_ITERATOR_WORKDIR) ? 0 : - ((workdir_iterator *)iter)->is_ignored; + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type != GIT_ITERATOR_WORKDIR) + return 0; + + if (wi->is_ignored != -1) + return wi->is_ignored; + + if (git_ignore__lookup(&wi->ignores, wi->entry.path, &wi->is_ignored) < 0) + wi->is_ignored = 1; + + return wi->is_ignored; } int git_iterator_advance_into_directory( diff --git a/src/iterator.h b/src/iterator.h index d7df50137..77ead76cc 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -52,13 +52,23 @@ GIT_INLINE(int) git_iterator_for_tree( } extern int git_iterator_for_index_range( - git_iterator **iter, git_repository *repo, + git_iterator **iter, git_index *index, const char *start, const char *end); GIT_INLINE(int) git_iterator_for_index( + git_iterator **iter, git_index *index) +{ + return git_iterator_for_index_range(iter, index, NULL, NULL); +} + +extern int git_iterator_for_repo_index_range( + git_iterator **iter, git_repository *repo, + const char *start, const char *end); + +GIT_INLINE(int) git_iterator_for_repo_index( git_iterator **iter, git_repository *repo) { - return git_iterator_for_index_range(iter, repo, NULL, NULL); + return git_iterator_for_repo_index_range(iter, repo, NULL, NULL); } extern int git_iterator_for_workdir_range( diff --git a/src/merge.c b/src/merge.c new file mode 100644 index 000000000..135af6a8c --- /dev/null +++ b/src/merge.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "repository.h" +#include "buffer.h" +#include "merge.h" +#include "refs.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "git2/reset.h" + +int git_merge__cleanup(git_repository *repo) +{ + int error = 0; + git_buf merge_head_path = GIT_BUF_INIT, + merge_mode_path = GIT_BUF_INIT, + merge_msg_path = GIT_BUF_INIT; + + assert(repo); + + if (git_buf_joinpath(&merge_head_path, repo->path_repository, GIT_MERGE_HEAD_FILE) < 0 || + git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0 || + git_buf_joinpath(&merge_mode_path, repo->path_repository, GIT_MERGE_MODE_FILE) < 0) + return -1; + + if (git_path_isfile(merge_head_path.ptr)) { + if ((error = p_unlink(merge_head_path.ptr)) < 0) + goto cleanup; + } + + if (git_path_isfile(merge_mode_path.ptr)) + (void)p_unlink(merge_mode_path.ptr); + + if (git_path_isfile(merge_msg_path.ptr)) + (void)p_unlink(merge_msg_path.ptr); + +cleanup: + git_buf_free(&merge_msg_path); + git_buf_free(&merge_mode_path); + git_buf_free(&merge_head_path); + + return error; +} + diff --git a/src/merge.h b/src/merge.h new file mode 100644 index 000000000..2117d9214 --- /dev/null +++ b/src/merge.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_merge_h__ +#define INCLUDE_merge_h__ + +#include "git2/types.h" + +#define GIT_MERGE_MSG_FILE "MERGE_MSG" +#define GIT_MERGE_MODE_FILE "MERGE_MODE" + +#define MERGE_CONFIG_FILE_MODE 0666 + +int git_merge__cleanup(git_repository *repo); + +#endif diff --git a/src/netops.c b/src/netops.c index df502e619..422ea63f1 100644 --- a/src/netops.c +++ b/src/netops.c @@ -14,12 +14,13 @@ #else # include <ws2tcpip.h> # ifdef _MSC_VER -# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "ws2_32") # endif #endif #ifdef GIT_SSL # include <openssl/ssl.h> +# include <openssl/err.h> # include <openssl/x509v3.h> #endif @@ -30,7 +31,6 @@ #include "netops.h" #include "posix.h" #include "buffer.h" -#include "transport.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -41,6 +41,8 @@ static void net_set_error(const char *str) size = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, 0, error, 0, (LPSTR)&err_str, 0, 0); + GIT_UNUSED(size); + giterr_set(GITERR_NET, "%s: %s", str, err_str); LocalFree(err_str); } @@ -108,8 +110,8 @@ static int gitno__recv_ssl(gitno_buffer *buf) int ret; do { - ret = SSL_read(buf->ssl->ssl, buf->data + buf->offset, buf->len - buf->offset); - } while (SSL_get_error(buf->ssl->ssl, ret) == SSL_ERROR_WANT_READ); + ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset); + } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ); if (ret < 0) { net_set_error("Error receiving socket data"); @@ -121,11 +123,11 @@ static int gitno__recv_ssl(gitno_buffer *buf) } #endif -int gitno__recv(gitno_buffer *buf) +static int gitno__recv(gitno_buffer *buf) { int ret; - ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0); if (ret < 0) { net_set_error("Error receiving socket data"); return -1; @@ -136,31 +138,31 @@ int gitno__recv(gitno_buffer *buf) } void gitno_buffer_setup_callback( - git_transport *t, + gitno_socket *socket, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data) { - memset(buf, 0x0, sizeof(gitno_buffer)); memset(data, 0x0, len); buf->data = data; buf->len = len; buf->offset = 0; - buf->fd = t->socket; + buf->socket = socket; buf->recv = recv; buf->cb_data = cb_data; } -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len) +void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len) { #ifdef GIT_SSL - if (t->use_ssl) { - gitno_buffer_setup_callback(t, buf, data, len, gitno__recv_ssl, NULL); - buf->ssl = &t->ssl; - } else + if (socket->ssl.ctx) { + gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL); + return; + } #endif - gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL); + + gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL); } /* Consume up to ptr and move the rest of the buffer to the beginning */ @@ -186,31 +188,26 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_ssl_teardown(git_transport *t) -{ #ifdef GIT_SSL - int ret; -#endif - - if (!t->use_ssl) - return 0; -#ifdef GIT_SSL +static int gitno_ssl_teardown(gitno_ssl *ssl) +{ + int ret; do { - ret = SSL_shutdown(t->ssl.ssl); + ret = SSL_shutdown(ssl->ssl); } while (ret == 0); + if (ret < 0) - return ssl_set_error(&t->ssl, ret); + ret = ssl_set_error(ssl, ret); + else + ret = 0; - SSL_free(t->ssl.ssl); - SSL_CTX_free(t->ssl.ctx); -#endif - return 0; + SSL_free(ssl->ssl); + SSL_CTX_free(ssl->ctx); + return ret; } - -#ifdef GIT_SSL /* Match host names according to RFC 2818 rules */ static int match_host(const char *pattern, const char *host) { @@ -261,7 +258,7 @@ static int check_host_name(const char *name, const char *host) return 0; } -static int verify_server_cert(git_transport *t, const char *host) +static int verify_server_cert(gitno_ssl *ssl, const char *host) { X509 *cert; X509_NAME *peer_name; @@ -274,24 +271,24 @@ static int verify_server_cert(git_transport *t, const char *host) void *addr; int i = -1,j; - if (SSL_get_verify_result(t->ssl.ssl) != X509_V_OK) { + if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) { giterr_set(GITERR_SSL, "The SSL certificate is invalid"); return -1; } /* Try to parse the host as an IP address to see if it is */ - if (inet_pton(AF_INET, host, &addr4)) { + if (p_inet_pton(AF_INET, host, &addr4)) { type = GEN_IPADD; addr = &addr4; } else { - if(inet_pton(AF_INET6, host, &addr6)) { + if(p_inet_pton(AF_INET6, host, &addr6)) { type = GEN_IPADD; addr = &addr6; } } - cert = SSL_get_peer_certificate(t->ssl.ssl); + cert = SSL_get_peer_certificate(ssl->ssl); /* Check the alternative names */ alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); @@ -375,7 +372,7 @@ static int verify_server_cert(git_transport *t, const char *host) on_error: OPENSSL_free(peer_cn); - return ssl_set_error(&t->ssl, 0); + return ssl_set_error(ssl, 0); cert_fail: OPENSSL_free(peer_cn); @@ -383,51 +380,81 @@ cert_fail: return -1; } -static int ssl_setup(git_transport *t, const char *host) +static int ssl_setup(gitno_socket *socket, const char *host, int flags) { int ret; SSL_library_init(); SSL_load_error_strings(); - t->ssl.ctx = SSL_CTX_new(SSLv23_method()); - if (t->ssl.ctx == NULL) - return ssl_set_error(&t->ssl, 0); + socket->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (socket->ssl.ctx == NULL) + return ssl_set_error(&socket->ssl, 0); - SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); - SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_NONE, NULL); - if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) - return ssl_set_error(&t->ssl, 0); + SSL_CTX_set_mode(socket->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(socket->ssl.ctx, SSL_VERIFY_NONE, NULL); + if (!SSL_CTX_set_default_verify_paths(socket->ssl.ctx)) + return ssl_set_error(&socket->ssl, 0); - t->ssl.ssl = SSL_new(t->ssl.ctx); - if (t->ssl.ssl == NULL) - return ssl_set_error(&t->ssl, 0); + socket->ssl.ssl = SSL_new(socket->ssl.ctx); + if (socket->ssl.ssl == NULL) + return ssl_set_error(&socket->ssl, 0); - if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) - return ssl_set_error(&t->ssl, ret); + if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0) + return ssl_set_error(&socket->ssl, ret); - if ((ret = SSL_connect(t->ssl.ssl)) <= 0) - return ssl_set_error(&t->ssl, ret); + if ((ret = SSL_connect(socket->ssl.ssl)) <= 0) + return ssl_set_error(&socket->ssl, ret); - if (t->check_cert && verify_server_cert(t, host) < 0) + if ((GITNO_CONNECT_SSL_NO_CHECK_CERT & flags) || verify_server_cert(&socket->ssl, host) < 0) return -1; return 0; } -#else -static int ssl_setup(git_transport *t, const char *host) +#endif + +static int gitno__close(GIT_SOCKET s) { - GIT_UNUSED(t); - GIT_UNUSED(host); +#ifdef GIT_WIN32 + if (SOCKET_ERROR == closesocket(s)) + return -1; + + if (0 != WSACleanup()) { + giterr_set(GITERR_OS, "Winsock cleanup failed"); + return -1; + } + return 0; -} +#else + return close(s); #endif +} -int gitno_connect(git_transport *t, const char *host, const char *port) +int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags) { struct addrinfo *info = NULL, *p; struct addrinfo hints; - int ret; GIT_SOCKET s = INVALID_SOCKET; + int ret; + +#ifdef GIT_WIN32 + /* on win32, the WSA context needs to be initialized + * before any socket calls can be performed */ + WSADATA wsd; + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } + + if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) { + WSACleanup(); + giterr_set(GITERR_OS, "Winsock init failed"); + return -1; + } +#endif + + /* Zero the socket structure provided */ + memset(s_out, 0x0, sizeof(gitno_socket)); memset(&hints, 0x0, sizeof(struct addrinfo)); hints.ai_socktype = SOCK_STREAM; @@ -451,7 +478,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) break; /* If we can't connect, try the next one */ - gitno_close(s); + gitno__close(s); s = INVALID_SOCKET; } @@ -461,46 +488,56 @@ int gitno_connect(git_transport *t, const char *host, const char *port) return -1; } - t->socket = s; + s_out->socket = s; p_freeaddrinfo(info); - if (t->use_ssl && ssl_setup(t, host) < 0) +#ifdef GIT_SSL + if ((flags & GITNO_CONNECT_SSL) && ssl_setup(s_out, host, flags) < 0) + return -1; +#else + /* SSL is not supported */ + if (flags & GITNO_CONNECT_SSL) { + giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2."); return -1; + } +#endif return 0; } #ifdef GIT_SSL -static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) +static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags) { int ret; size_t off = 0; + GIT_UNUSED(flags); + while (off < len) { ret = SSL_write(ssl->ssl, msg + off, len - off); if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) return ssl_set_error(ssl, ret); off += ret; - } + } return off; } #endif -int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags) { int ret; size_t off = 0; #ifdef GIT_SSL - if (t->use_ssl) - return send_ssl(&t->ssl, msg, len); + if (socket->ssl.ctx) + return gitno_send_ssl(&socket->ssl, msg, len, flags); #endif while (off < len) { errno = 0; - ret = p_send(t->socket, msg + off, len - off, flags); + ret = p_send(socket->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1; @@ -512,19 +549,17 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags) return (int)off; } - -#ifdef GIT_WIN32 -int gitno_close(GIT_SOCKET s) +int gitno_close(gitno_socket *s) { - return closesocket(s) == SOCKET_ERROR ? -1 : 0; -} -#else -int gitno_close(GIT_SOCKET s) -{ - return close(s); -} +#ifdef GIT_SSL + if (s->ssl.ctx && + gitno_ssl_teardown(&s->ssl) < 0) + return -1; #endif + return gitno__close(s->socket); +} + int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) { fd_set fds; @@ -534,10 +569,10 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec) tv.tv_usec = usec; FD_ZERO(&fds); - FD_SET(buf->fd, &fds); + FD_SET(buf->socket->socket, &fds); /* The select(2) interface is silly */ - return select((int)buf->fd + 1, &fds, NULL, NULL, &tv); + return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv); } int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port) diff --git a/src/netops.h b/src/netops.h index 7c53fd0dc..efbbc65a4 100644 --- a/src/netops.h +++ b/src/netops.h @@ -10,31 +10,60 @@ #include "posix.h" #include "common.h" +#ifdef GIT_SSL +# include <openssl/ssl.h> +#endif + +struct gitno_ssl { +#ifdef GIT_SSL + SSL_CTX *ctx; + SSL *ssl; +#else + size_t dummy; +#endif +}; + +typedef struct gitno_ssl gitno_ssl; + +/* Represents a socket that may or may not be using SSL */ +struct gitno_socket { + GIT_SOCKET socket; + gitno_ssl ssl; +}; + +typedef struct gitno_socket gitno_socket; + struct gitno_buffer { char *data; size_t len; size_t offset; - GIT_SOCKET fd; -#ifdef GIT_SSL - struct gitno_ssl *ssl; -#endif - int (*recv)(gitno_buffer *buffer); + gitno_socket *socket; + int (*recv)(struct gitno_buffer *buffer); void *cb_data; }; -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, size_t len); -void gitno_buffer_setup_callback(git_transport *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); +typedef struct gitno_buffer gitno_buffer; + +/* Flags to gitno_connect */ +enum { + /* Attempt to create an SSL connection. */ + GITNO_CONNECT_SSL = 1, + + /* Valid only when GITNO_CONNECT_SSL is also specified. + * Indicates that the server certificate should not be validated. */ + GITNO_CONNECT_SSL_NO_CHECK_CERT = 2, +}; + +void gitno_buffer_setup(gitno_socket *t, gitno_buffer *buf, char *data, size_t len); +void gitno_buffer_setup_callback(gitno_socket *t, gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data); int gitno_recv(gitno_buffer *buf); -int gitno__recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(git_transport *t, const char *host, const char *port); -int gitno_send(git_transport *t, const char *msg, size_t len, int flags); -int gitno_close(GIT_SOCKET s); -int gitno_ssl_teardown(git_transport *t); -int gitno_send_chunk_size(int s, size_t len); +int gitno_connect(gitno_socket *socket, const char *host, const char *port, int flags); +int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags); +int gitno_close(gitno_socket *s); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port); @@ -23,6 +23,8 @@ #define GIT_LOOSE_PRIORITY 2 #define GIT_PACKED_PRIORITY 1 +#define GIT_ALTERNATES_MAX_DEPTH 5 + typedef struct { git_odb_backend *backend; @@ -30,6 +32,8 @@ typedef struct int is_alternate; } backend_internal; +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); + static int format_object_header(char *hdr, size_t n, size_t obj_len, git_otype obj_type) { const char *type_str = git_object_type2string(obj_type); @@ -117,23 +121,27 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) { int hdr_len; char hdr[64], buffer[2048]; - git_hash_ctx *ctx; + git_hash_ctx ctx; ssize_t read_len = 0; + int error = 0; if (!git_object_typeisloose(type)) { giterr_set(GITERR_INVALID, "Invalid object type for hash"); return -1; } - hdr_len = format_object_header(hdr, sizeof(hdr), size, type); + if ((error = git_hash_ctx_init(&ctx)) < 0) + return -1; - ctx = git_hash_new_ctx(); - GITERR_CHECK_ALLOC(ctx); + hdr_len = format_object_header(hdr, sizeof(hdr), size, type); - git_hash_update(ctx, hdr, hdr_len); + if ((error = git_hash_update(&ctx, hdr, hdr_len)) < 0) + goto done; while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { - git_hash_update(ctx, buffer, read_len); + if ((error = git_hash_update(&ctx, buffer, read_len)) < 0) + goto done; + size -= read_len; } @@ -141,15 +149,18 @@ int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type) * If size is not zero, the file was truncated after we originally * stat'd it, so we consider this a read failure too */ if (read_len < 0 || size > 0) { - git_hash_free_ctx(ctx); giterr_set(GITERR_OS, "Error reading file for hashing"); + error = -1; + + goto done; return -1; } - git_hash_final(out, ctx); - git_hash_free_ctx(ctx); + error = git_hash_final(out, &ctx); - return 0; +done: + git_hash_ctx_cleanup(&ctx); + return error; } int git_odb__hashfd_filtered( @@ -388,7 +399,7 @@ int git_odb_add_alternate(git_odb *odb, git_odb_backend *backend, int priority) return add_backend_internal(odb, backend, priority, 1); } -static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates) +static int add_default_backends(git_odb *db, const char *objects_dir, int as_alternates, int alternate_depth) { git_odb_backend *loose, *packed; @@ -402,10 +413,10 @@ static int add_default_backends(git_odb *db, const char *objects_dir, int as_alt add_backend_internal(db, packed, GIT_PACKED_PRIORITY, as_alternates) < 0) return -1; - return 0; + return load_alternates(db, objects_dir, alternate_depth); } -static int load_alternates(git_odb *odb, const char *objects_dir) +static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth) { git_buf alternates_path = GIT_BUF_INIT; git_buf alternates_buf = GIT_BUF_INIT; @@ -413,6 +424,11 @@ static int load_alternates(git_odb *odb, const char *objects_dir) const char *alternate; int result = 0; + /* Git reports an error, we just ignore anything deeper */ + if (alternate_depth > GIT_ALTERNATES_MAX_DEPTH) { + return 0; + } + if (git_buf_joinpath(&alternates_path, objects_dir, GIT_ALTERNATES_FILE) < 0) return -1; @@ -433,14 +449,18 @@ static int load_alternates(git_odb *odb, const char *objects_dir) if (*alternate == '\0' || *alternate == '#') continue; - /* relative path: build based on the current `objects` folder */ - if (*alternate == '.') { + /* + * Relative path: build based on the current `objects` + * folder. However, relative paths are only allowed in + * the current repository. + */ + if (*alternate == '.' && !alternate_depth) { if ((result = git_buf_joinpath(&alternates_path, objects_dir, alternate)) < 0) break; alternate = git_buf_cstr(&alternates_path); } - if ((result = add_default_backends(odb, alternate, 1)) < 0) + if ((result = add_default_backends(odb, alternate, 1, alternate_depth + 1)) < 0) break; } @@ -461,8 +481,7 @@ int git_odb_open(git_odb **out, const char *objects_dir) if (git_odb_new(&db) < 0) return -1; - if (add_default_backends(db, objects_dir, 0) < 0 || - load_alternates(db, objects_dir) < 0) + if (add_default_backends(db, objects_dir, 0, 0) < 0) { git_odb_free(db); return -1; @@ -766,6 +785,31 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi return error; } +int git_odb_write_pack(struct git_odb_writepack **out, git_odb *db, git_transfer_progress_callback progress_cb, void *progress_payload) +{ + unsigned int i; + int error = GIT_ERROR; + + assert(out && db); + + for (i = 0; i < db->backends.length && error < 0; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + /* we don't write in alternates! */ + if (internal->is_alternate) + continue; + + if (b->writepack != NULL) + error = b->writepack(out, b, progress_cb, progress_payload); + } + + if (error == GIT_PASSTHROUGH) + error = 0; + + return error; +} + void * git_odb_backend_malloc(git_odb_backend *backend, size_t len) { GIT_UNUSED(backend); diff --git a/src/odb_pack.c b/src/odb_pack.c index 964e82afb..9f7a6ee1f 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -26,6 +26,11 @@ struct pack_backend { char *pack_folder; }; +struct pack_writepack { + struct git_odb_writepack parent; + git_indexer_stream *indexer_stream; +}; + /** * The wonderful tale of a Packed Object lookup query * =================================================== @@ -475,6 +480,67 @@ static int pack_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *o return 0; } +static int pack_backend__writepack_add(struct git_odb_writepack *_writepack, const void *data, size_t size, git_transfer_progress *stats) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + assert(writepack); + + return git_indexer_stream_add(writepack->indexer_stream, data, size, stats); +} + +static int pack_backend__writepack_commit(struct git_odb_writepack *_writepack, git_transfer_progress *stats) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + assert(writepack); + + return git_indexer_stream_finalize(writepack->indexer_stream, stats); +} + +static void pack_backend__writepack_free(struct git_odb_writepack *_writepack) +{ + struct pack_writepack *writepack = (struct pack_writepack *)_writepack; + + assert(writepack); + + git_indexer_stream_free(writepack->indexer_stream); + git__free(writepack); +} + +static int pack_backend__writepack(struct git_odb_writepack **out, + git_odb_backend *_backend, + git_transfer_progress_callback progress_cb, + void *progress_payload) +{ + struct pack_backend *backend; + struct pack_writepack *writepack; + + assert(out && _backend); + + *out = NULL; + + backend = (struct pack_backend *)_backend; + + writepack = git__calloc(1, sizeof(struct pack_writepack)); + GITERR_CHECK_ALLOC(writepack); + + if (git_indexer_stream_new(&writepack->indexer_stream, + backend->pack_folder, progress_cb, progress_payload) < 0) { + git__free(writepack); + return -1; + } + + writepack->parent.backend = _backend; + writepack->parent.add = pack_backend__writepack_add; + writepack->parent.commit = pack_backend__writepack_commit; + writepack->parent.free = pack_backend__writepack_free; + + *out = (git_odb_writepack *)writepack; + + return 0; +} + static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; @@ -553,6 +619,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) backend->parent.read_header = NULL; backend->parent.exists = &pack_backend__exists; backend->parent.foreach = &pack_backend__foreach; + backend->parent.writepack = &pack_backend__writepack; backend->parent.free = &pack_backend__free; *backend_out = (git_odb_backend *)backend; diff --git a/src/pack-objects.c b/src/pack-objects.c index eb76e05a2..a146dc048 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -73,16 +73,16 @@ static int packbuilder_config(git_packbuilder *pb) { git_config *config; int ret; + int64_t val; if (git_repository_config__weakptr(&config, pb->repo) < 0) return -1; -#define config_get(key, dst, default) \ - ret = git_config_get_int64((int64_t *)&dst, config, key); \ - if (ret == GIT_ENOTFOUND) \ - dst = default; \ - else if (ret < 0) \ - return -1; +#define config_get(KEY,DST,DFLT) do { \ + ret = git_config_get_int64(&val, config, KEY); \ + if (!ret) (DST) = val; \ + else if (ret == GIT_ENOTFOUND) (DST) = (DFLT); \ + else if (ret < 0) return -1; } while (0) config_get("pack.deltaCacheSize", pb->max_delta_cache_size, GIT_PACK_DELTA_CACHE_SIZE); @@ -103,7 +103,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) *out = NULL; - pb = git__calloc(sizeof(*pb), 1); + pb = git__calloc(1, sizeof(*pb)); GITERR_CHECK_ALLOC(pb); pb->object_ix = git_oidmap_alloc(); @@ -113,9 +113,8 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) pb->repo = repo; pb->nr_threads = 1; /* do not spawn any thread by default */ - pb->ctx = git_hash_new_ctx(); - if (!pb->ctx || + if (git_hash_ctx_init(&pb->ctx) < 0 || git_repository_odb(&pb->odb, repo) < 0 || packbuilder_config(pb) < 0) goto on_error; @@ -297,14 +296,13 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po) if (git_buf_put(buf, (char *)hdr, hdr_len) < 0) goto on_error; - git_hash_update(pb->ctx, hdr, hdr_len); + if (git_hash_update(&pb->ctx, hdr, hdr_len) < 0) + goto on_error; if (type == GIT_OBJ_REF_DELTA) { - if (git_buf_put(buf, (char *)po->delta->id.id, - GIT_OID_RAWSZ) < 0) + if (git_buf_put(buf, (char *)po->delta->id.id, GIT_OID_RAWSZ) < 0 || + git_hash_update(&pb->ctx, po->delta->id.id, GIT_OID_RAWSZ) < 0) goto on_error; - - git_hash_update(pb->ctx, po->delta->id.id, GIT_OID_RAWSZ); } /* Write data */ @@ -319,11 +317,10 @@ static int write_object(git_buf *buf, git_packbuilder *pb, git_pobject *po) size = zbuf.size; } - if (git_buf_put(buf, data, size) < 0) + if (git_buf_put(buf, data, size) < 0 || + git_hash_update(&pb->ctx, data, size) < 0) goto on_error; - git_hash_update(pb->ctx, data, size); - if (po->delta_data) git__free(po->delta_data); @@ -573,7 +570,8 @@ static int write_pack(git_packbuilder *pb, if (cb(&ph, sizeof(ph), data) < 0) goto on_error; - git_hash_update(pb->ctx, &ph, sizeof(ph)); + if (git_hash_update(&pb->ctx, &ph, sizeof(ph)) < 0) + goto on_error; pb->nr_remaining = pb->nr_objects; do { @@ -592,7 +590,9 @@ static int write_pack(git_packbuilder *pb, git__free(write_order); git_buf_free(&buf); - git_hash_final(&pb->pack_oid, pb->ctx); + + if (git_hash_final(&pb->pack_oid, &pb->ctx) < 0) + goto on_error; return cb(pb->pack_oid.id, GIT_OID_RAWSZ, data); @@ -604,8 +604,8 @@ on_error: static int send_pack_file(void *buf, size_t size, void *data) { - git_transport *t = (git_transport *)data; - return gitno_send(t, buf, size, 0); + gitno_socket *s = (gitno_socket *)data; + return gitno_send(s, buf, size, 0); } static int write_pack_buf(void *buf, size_t size, void *data) @@ -1231,10 +1231,16 @@ static int prepare_pack(git_packbuilder *pb) #define PREPARE_PACK if (prepare_pack(pb) < 0) { return -1; } -int git_packbuilder_send(git_packbuilder *pb, git_transport *t) +int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s) { PREPARE_PACK; - return write_pack(pb, &send_pack_file, t); + return write_pack(pb, &send_pack_file, s); +} + +int git_packbuilder_foreach(git_packbuilder *pb, int (*cb)(void *buf, size_t size, void *payload), void *payload) +{ + PREPARE_PACK; + return write_pack(pb, cb, payload); } int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb) @@ -1286,6 +1292,16 @@ int git_packbuilder_insert_tree(git_packbuilder *pb, const git_oid *oid) return 0; } +uint32_t git_packbuilder_object_count(git_packbuilder *pb) +{ + return pb->nr_objects; +} + +uint32_t git_packbuilder_written(git_packbuilder *pb) +{ + return pb->nr_written; +} + void git_packbuilder_free(git_packbuilder *pb) { if (pb == NULL) @@ -1302,14 +1318,13 @@ void git_packbuilder_free(git_packbuilder *pb) if (pb->odb) git_odb_free(pb->odb); - if (pb->ctx) - git_hash_free_ctx(pb->ctx); - if (pb->object_ix) git_oidmap_free(pb->object_ix); if (pb->object_list) git__free(pb->object_list); + git_hash_ctx_cleanup(&pb->ctx); + git__free(pb); } diff --git a/src/pack-objects.h b/src/pack-objects.h index 0d4854d0d..e34cc2754 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -13,6 +13,7 @@ #include "buffer.h" #include "hash.h" #include "oidmap.h" +#include "netops.h" #include "git2/oid.h" @@ -51,7 +52,7 @@ struct git_packbuilder { git_repository *repo; /* associated repository */ git_odb *odb; /* associated object database */ - git_hash_ctx *ctx; + git_hash_ctx ctx; uint32_t nr_objects, nr_alloc, @@ -70,18 +71,18 @@ struct git_packbuilder { git_cond progress_cond; /* configs */ - unsigned long delta_cache_size; - unsigned long max_delta_cache_size; - unsigned long cache_max_small_delta_size; - unsigned long big_file_threshold; - unsigned long window_memory_limit; + uint64_t delta_cache_size; + uint64_t max_delta_cache_size; + uint64_t cache_max_small_delta_size; + uint64_t big_file_threshold; + uint64_t window_memory_limit; int nr_threads; /* nr of threads to use */ bool done; }; -int git_packbuilder_send(git_packbuilder *pb, git_transport *t); +int git_packbuilder_send(git_packbuilder *pb, gitno_socket *s); int git_packbuilder_write_buf(git_buf *buf, git_packbuilder *pb); #endif /* INCLUDE_pack_objects_h__ */ diff --git a/src/path.c b/src/path.c index 09556bd3f..98351bec3 100644 --- a/src/path.c +++ b/src/path.c @@ -382,9 +382,10 @@ int git_path_walk_up( iter.asize = path->asize; while (scan >= stop) { - if ((error = cb(data, &iter)) < 0) - break; + error = cb(data, &iter); iter.ptr[scan] = oldc; + if (error < 0) + break; scan = git_buf_rfind_next(&iter, '/'); if (scan >= 0) { scan++; diff --git a/src/pathspec.c b/src/pathspec.c new file mode 100644 index 000000000..fc6547afe --- /dev/null +++ b/src/pathspec.c @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "pathspec.h" +#include "attr_file.h" + +/* what is the common non-wildcard prefix for all items in the pathspec */ +char *git_pathspec_prefix(const git_strarray *pathspec) +{ + git_buf prefix = GIT_BUF_INIT; + const char *scan; + + if (!pathspec || !pathspec->count || + git_buf_common_prefix(&prefix, pathspec) < 0) + return NULL; + + /* diff prefix will only be leading non-wildcards */ + for (scan = prefix.ptr; *scan; ++scan) { + if (git__iswildcard(*scan) && + (scan == prefix.ptr || (*(scan - 1) != '\\'))) + break; + } + git_buf_truncate(&prefix, scan - prefix.ptr); + + if (prefix.size <= 0) { + git_buf_free(&prefix); + return NULL; + } + + git_buf_unescape(&prefix); + + return git_buf_detach(&prefix); +} + +/* is there anything in the spec that needs to be filtered on */ +bool git_pathspec_is_interesting(const git_strarray *pathspec) +{ + const char *str; + + if (pathspec == NULL || pathspec->count == 0) + return false; + if (pathspec->count > 1) + return true; + + str = pathspec->strings[0]; + if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.'))) + return false; + return true; +} + +/* build a vector of fnmatch patterns to evaluate efficiently */ +int git_pathspec_init( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool) +{ + size_t i; + + memset(vspec, 0, sizeof(*vspec)); + + if (!git_pathspec_is_interesting(strspec)) + return 0; + + if (git_vector_init(vspec, strspec->count, NULL) < 0) + return -1; + + for (i = 0; i < strspec->count; ++i) { + int ret; + const char *pattern = strspec->strings[i]; + git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch)); + if (!match) + return -1; + + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE; + + ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); + if (ret == GIT_ENOTFOUND) { + git__free(match); + continue; + } else if (ret < 0) + return ret; + + if (git_vector_insert(vspec, match) < 0) + return -1; + } + + return 0; +} + +/* free data from the pathspec vector */ +void git_pathspec_free(git_vector *vspec) +{ + git_attr_fnmatch *match; + unsigned int i; + + git_vector_foreach(vspec, i, match) { + git__free(match); + vspec->contents[i] = NULL; + } + + git_vector_free(vspec); +} + +/* match a path against the vectorized pathspec */ +bool git_pathspec_match_path( + git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold) +{ + unsigned int i; + git_attr_fnmatch *match; + int fnmatch_flags = 0; + int (*use_strcmp)(const char *, const char *); + int (*use_strncmp)(const char *, const char *, size_t); + + if (!vspec || !vspec->length) + return true; + + if (disable_fnmatch) + fnmatch_flags = -1; + else if (casefold) + fnmatch_flags = FNM_CASEFOLD; + + if (casefold) { + use_strcmp = git__strcasecmp; + use_strncmp = git__strncasecmp; + } else { + use_strcmp = git__strcmp; + use_strncmp = git__strncmp; + } + + git_vector_foreach(vspec, i, match) { + int result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0; + + if (fnmatch_flags >= 0 && result == FNM_NOMATCH) + result = p_fnmatch(match->pattern, path, fnmatch_flags); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + use_strncmp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + } + + return false; +} + diff --git a/src/pathspec.h b/src/pathspec.h new file mode 100644 index 000000000..31a1cdad9 --- /dev/null +++ b/src/pathspec.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_pathspec_h__ +#define INCLUDE_pathspec_h__ + +#include "common.h" +#include "buffer.h" +#include "vector.h" +#include "pool.h" + +/* what is the common non-wildcard prefix for all items in the pathspec */ +extern char *git_pathspec_prefix(const git_strarray *pathspec); + +/* is there anything in the spec that needs to be filtered on */ +extern bool git_pathspec_is_interesting(const git_strarray *pathspec); + +/* build a vector of fnmatch patterns to evaluate efficiently */ +extern int git_pathspec_init( + git_vector *vspec, const git_strarray *strspec, git_pool *strpool); + +/* free data from the pathspec vector */ +extern void git_pathspec_free(git_vector *vspec); + +/* match a path against the vectorized pathspec */ +extern bool git_pathspec_match_path( + git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold); + +#endif diff --git a/src/pkt.h b/src/pkt.h deleted file mode 100644 index 0fdb5c7cd..000000000 --- a/src/pkt.h +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_pkt_h__ -#define INCLUDE_pkt_h__ - -#include "common.h" -#include "transport.h" -#include "buffer.h" -#include "posix.h" -#include "git2/net.h" - -enum git_pkt_type { - GIT_PKT_CMD, - GIT_PKT_FLUSH, - GIT_PKT_REF, - GIT_PKT_HAVE, - GIT_PKT_ACK, - GIT_PKT_NAK, - GIT_PKT_PACK, - GIT_PKT_COMMENT, - GIT_PKT_ERR, - GIT_PKT_DATA, - GIT_PKT_PROGRESS, -}; - -/* Used for multi-ack */ -enum git_ack_status { - GIT_ACK_NONE, - GIT_ACK_CONTINUE, - GIT_ACK_COMMON, - GIT_ACK_READY -}; - -/* This would be a flush pkt */ -typedef struct { - enum git_pkt_type type; -} git_pkt; - -struct git_pkt_cmd { - enum git_pkt_type type; - char *cmd; - char *path; - char *host; -}; - -/* This is a pkt-line with some info in it */ -typedef struct { - enum git_pkt_type type; - git_remote_head head; - char *capabilities; -} git_pkt_ref; - -/* Useful later */ -typedef struct { - enum git_pkt_type type; - git_oid oid; - enum git_ack_status status; -} git_pkt_ack; - -typedef struct { - enum git_pkt_type type; - char comment[GIT_FLEX_ARRAY]; -} git_pkt_comment; - -typedef struct { - enum git_pkt_type type; - int len; - char data[GIT_FLEX_ARRAY]; -} git_pkt_data; - -typedef git_pkt_data git_pkt_progress; - -typedef struct { - enum git_pkt_type type; - char error[GIT_FLEX_ARRAY]; -} git_pkt_err; - -int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); -int git_pkt_buffer_flush(git_buf *buf); -int git_pkt_send_flush(GIT_SOCKET s); -int git_pkt_buffer_done(git_buf *buf); -int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf); -int git_pkt_buffer_have(git_oid *oid, git_buf *buf); -void git_pkt_free(git_pkt *pkt); - -#endif diff --git a/src/posix.c b/src/posix.c index 985221dd5..d207ce1a0 100644 --- a/src/posix.c +++ b/src/posix.c @@ -205,3 +205,5 @@ int p_write(git_file fd, const void *buf, size_t cnt) } return 0; } + + diff --git a/src/protocol.c b/src/protocol.c deleted file mode 100644 index affad5173..000000000 --- a/src/protocol.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#include "common.h" -#include "protocol.h" -#include "pkt.h" -#include "buffer.h" - -int git_protocol_store_refs(git_transport *t, int flushes) -{ - gitno_buffer *buf = &t->buffer; - git_vector *refs = &t->refs; - int error, flush = 0, recvd; - const char *line_end; - git_pkt *pkt; - - do { - if (buf->offset > 0) - error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); - else - error = GIT_EBUFS; - - if (error < 0 && error != GIT_EBUFS) - return -1; - - if (error == GIT_EBUFS) { - if ((recvd = gitno_recv(buf)) < 0) - return -1; - - if (recvd == 0 && !flush) { - giterr_set(GITERR_NET, "Early EOF"); - return -1; - } - - continue; - } - - gitno_consume(buf, line_end); - if (pkt->type == GIT_PKT_ERR) { - giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); - git__free(pkt); - return -1; - } - - if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) - return -1; - - if (pkt->type == GIT_PKT_FLUSH) { - flush++; - git_pkt_free(pkt); - } - } while (flush < flushes); - - return flush; -} - -int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps) -{ - const char *ptr; - - /* No refs or capabilites, odd but not a problem */ - if (pkt == NULL || pkt->capabilities == NULL) - return 0; - - ptr = pkt->capabilities; - while (ptr != NULL && *ptr != '\0') { - if (*ptr == ' ') - ptr++; - - if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { - caps->common = caps->ofs_delta = 1; - ptr += strlen(GIT_CAP_OFS_DELTA); - continue; - } - - if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { - caps->common = caps->multi_ack = 1; - ptr += strlen(GIT_CAP_MULTI_ACK); - continue; - } - - if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { - caps->common = caps->include_tag = 1; - ptr += strlen(GIT_CAP_INCLUDE_TAG); - continue; - } - - /* Keep side-band check after side-band-64k */ - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { - caps->common = caps->side_band_64k = 1; - ptr += strlen(GIT_CAP_SIDE_BAND_64K); - continue; - } - - if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { - caps->common = caps->side_band = 1; - ptr += strlen(GIT_CAP_SIDE_BAND); - continue; - } - - - /* We don't know this capability, so skip it */ - ptr = strchr(ptr, ' '); - } - - return 0; -} diff --git a/src/protocol.h b/src/protocol.h deleted file mode 100644 index a990938e5..000000000 --- a/src/protocol.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_protocol_h__ -#define INCLUDE_protocol_h__ - -#include "transport.h" -#include "buffer.h" -#include "pkt.h" - -int git_protocol_store_refs(git_transport *t, int flushes); -int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps); - -#define GIT_SIDE_BAND_DATA 1 -#define GIT_SIDE_BAND_PROGRESS 2 -#define GIT_SIDE_BAND_ERROR 3 - -#endif diff --git a/src/reflog.c b/src/reflog.c index a1ea7a27d..7b07c6a9f 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -166,6 +166,9 @@ void git_reflog_free(git_reflog *reflog) unsigned int i; git_reflog_entry *entry; + if (reflog == NULL) + return; + for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); @@ -185,7 +188,10 @@ static int retrieve_reflog_path(git_buf *path, git_reference *ref) static int create_new_reflog_file(const char *filepath) { - int fd; + int fd, error; + + if ((error = git_futils_mkpath2file(filepath, GIT_REFLOG_DIR_MODE)) < 0) + return error; if ((fd = p_open(filepath, O_WRONLY | O_CREAT | O_TRUNC, @@ -285,8 +291,8 @@ success: int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg) { - int count; git_reflog_entry *entry; + const git_reflog_entry *previous; const char *newline; assert(reflog && new_oid && committer); @@ -313,16 +319,12 @@ int git_reflog_append(git_reflog *reflog, const git_oid *new_oid, } } - count = git_reflog_entrycount(reflog); + previous = git_reflog_entry_byindex(reflog, 0); - if (count == 0) + if (previous == NULL) git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); - else { - const git_reflog_entry *previous; - - previous = git_reflog_entry_byindex(reflog, count -1); + else git_oid_cpy(&entry->oid_old, &previous->oid_cur); - } git_oid_cpy(&entry->oid_cur, new_oid); @@ -372,7 +374,7 @@ int git_reflog_rename(git_reference *ref, const char *new_name) goto cleanup; if (git_path_isdir(git_buf_cstr(&new_path)) && - (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) + (git_futils_rmdir_r(git_buf_cstr(&new_path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) goto cleanup; if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) @@ -411,8 +413,16 @@ unsigned int git_reflog_entrycount(git_reflog *reflog) const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) { + int pos; + assert(reflog); - return git_vector_get(&reflog->entries, idx); + + pos = git_reflog_entrycount(reflog) - (idx + 1); + + if (pos < 0) + return NULL; + + return git_vector_get(&reflog->entries, pos); } const git_oid * git_reflog_entry_oidold(const git_reflog_entry *entry) @@ -441,7 +451,7 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) int git_reflog_drop( git_reflog *reflog, - unsigned int idx, + size_t idx, int rewrite_previous_entry) { unsigned int entrycount; @@ -451,31 +461,32 @@ int git_reflog_drop( entrycount = git_reflog_entrycount(reflog); - if (idx >= entrycount) + entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + + if (entry == NULL) return GIT_ENOTFOUND; - entry = git_vector_get(&reflog->entries, idx); reflog_entry_free(entry); - if (git_vector_remove(&reflog->entries, idx) < 0) + if (git_vector_remove(&reflog->entries, entrycount - (idx + 1)) < 0) return -1; if (!rewrite_previous_entry) return 0; - /* No need to rewrite anything when removing the first entry */ + /* No need to rewrite anything when removing the most recent entry */ if (idx == 0) return 0; - /* There are no more entries in the log */ + /* Have the latest entry just been dropped? */ if (entrycount == 1) return 0; entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx - 1); - /* If the last entry has just been removed... */ + /* If the oldest entry has just been removed... */ if (idx == entrycount - 1) { - /* ...clear the oid_old member of the "new" last entry */ + /* ...clear the oid_old member of the "new" oldest entry */ if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) return -1; diff --git a/src/refs.c b/src/refs.c index 833f2fcc8..97c97563e 100644 --- a/src/refs.c +++ b/src/refs.c @@ -123,7 +123,8 @@ static int reference_read( if (git_buf_joinpath(&path, repo_path, ref_name) < 0) return -1; - result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); + result = git_futils_readbuffer_updated( + file_content, path.ptr, mtime, NULL, updated); git_buf_free(&path); return result; @@ -273,18 +274,15 @@ static int loose_write(git_reference *ref) git_buf ref_path = GIT_BUF_INIT; struct stat st; - if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) - return -1; - /* Remove a possibly existing empty directory hierarchy * which name would collide with the reference name */ - if (git_path_isdir(git_buf_cstr(&ref_path)) && - git_futils_rmdir_r(git_buf_cstr(&ref_path), NULL, - GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0) { - git_buf_free(&ref_path); + if (git_futils_rmdir_r(ref->name, ref->owner->path_repository, + GIT_RMDIR_SKIP_NONEMPTY) < 0) + return -1; + + if (git_buf_joinpath(&ref_path, ref->owner->path_repository, ref->name) < 0) return -1; - } if (git_filebuf_open(&file, ref_path.ptr, GIT_FILEBUF_FORCE) < 0) { git_buf_free(&ref_path); @@ -1948,10 +1946,10 @@ int git_reference_peel( peel_error(error, ref, "Cannot retrieve reference target"); goto cleanup; } - + if (target_type == GIT_OBJ_ANY && git_object_type(target) != GIT_OBJ_TAG) error = git_object__dup(peeled, target); - else + else error = git_object_peel(peeled, target, target_type); cleanup: diff --git a/src/refs.h b/src/refs.h index 54359f07b..a58bebd0d 100644 --- a/src/refs.h +++ b/src/refs.h @@ -28,10 +28,22 @@ #define GIT_PACKEDREFS_FILE_MODE 0666 #define GIT_HEAD_FILE "HEAD" +#define GIT_ORIG_HEAD_FILE "ORIG_HEAD" #define GIT_FETCH_HEAD_FILE "FETCH_HEAD" #define GIT_MERGE_HEAD_FILE "MERGE_HEAD" +#define GIT_REVERT_HEAD_FILE "REVERT_HEAD" +#define GIT_CHERRY_PICK_HEAD_FILE "CHERRY_PICK_HEAD" +#define GIT_BISECT_LOG_FILE "BISECT_LOG" +#define GIT_REBASE_MERGE_DIR "rebase-merge/" +#define GIT_REBASE_MERGE_INTERACTIVE_FILE GIT_REBASE_MERGE_DIR "interactive" +#define GIT_REBASE_APPLY_DIR "rebase-apply/" +#define GIT_REBASE_APPLY_REBASING_FILE GIT_REBASE_APPLY_DIR "rebasing" +#define GIT_REBASE_APPLY_APPLYING_FILE GIT_REBASE_APPLY_DIR "applying" #define GIT_REFS_HEADS_MASTER_FILE GIT_REFS_HEADS_DIR "master" +#define GIT_STASH_FILE "stash" +#define GIT_REFS_STASH_FILE GIT_REFS_DIR GIT_STASH_FILE + #define GIT_REFNAME_MAX 1024 struct git_reference { diff --git a/src/refspec.c b/src/refspec.c index b1790b32c..4d9915b7a 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -225,3 +225,21 @@ int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *n return refspec_transform(out, spec->dst, spec->src, name); } +int git_refspec__serialize(git_buf *out, const git_refspec *refspec) +{ + if (refspec->force) + git_buf_putc(out, '+'); + + git_buf_printf(out, "%s:%s", + refspec->src != NULL ? refspec->src : "", + refspec->dst != NULL ? refspec->dst : ""); + + return git_buf_oom(out) == false; +} + +int git_refspec_is_wildcard(const git_refspec *spec) +{ + assert(spec && spec->src); + + return (spec->src[strlen(spec->src) - 1] == '*'); +} diff --git a/src/refspec.h b/src/refspec.h index 6e0596a55..e27314cc3 100644 --- a/src/refspec.h +++ b/src/refspec.h @@ -51,4 +51,14 @@ int git_refspec_transform_r(git_buf *out, const git_refspec *spec, const char *n */ int git_refspec_transform_l(git_buf *out, const git_refspec *spec, const char *name); +int git_refspec__serialize(git_buf *out, const git_refspec *refspec); + +/** + * Determines if a refspec is a wildcard refspec. + * + * @param spec the refspec + * @return 1 if the refspec is a wildcard, 0 otherwise + */ +int git_refspec_is_wildcard(const git_refspec *spec); + #endif diff --git a/src/remote.c b/src/remote.c index e05ea059f..4a4d160eb 100644 --- a/src/remote.c +++ b/src/remote.c @@ -14,7 +14,8 @@ #include "remote.h" #include "fetch.h" #include "refs.h" -#include "pkt.h" +#include "refspec.h" +#include "fetchhead.h" #include <regex.h> @@ -69,6 +70,7 @@ int git_remote_new(git_remote **out, git_repository *repo, const char *name, con memset(remote, 0x0, sizeof(git_remote)); remote->repo = repo; remote->check_cert = 1; + remote->update_fetchhead = 1; if (git_vector_init(&remote->refs, 32, NULL) < 0) return -1; @@ -117,6 +119,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) memset(remote, 0x0, sizeof(git_remote)); remote->check_cert = 1; + remote->update_fetchhead = 1; remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); @@ -132,6 +135,12 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if ((error = git_config_get_string(&val, config, git_buf_cstr(&buf))) < 0) goto cleanup; + + if (strlen(val) == 0) { + giterr_set(GITERR_INVALID, "Malformed remote '%s' - missing URL", name); + error = -1; + goto cleanup; + } remote->repo = repo; remote->url = git__strdup(val); @@ -144,8 +153,10 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) } error = git_config_get_string(&val, config, git_buf_cstr(&buf)); - if (error == GIT_ENOTFOUND) + if (error == GIT_ENOTFOUND) { + val = NULL; error = 0; + } if (error < 0) { error = -1; @@ -201,12 +212,75 @@ cleanup: return error; } +static int ensure_remote_name_is_valid(const char *name) +{ + git_buf buf = GIT_BUF_INIT; + git_refspec refspec; + int error = -1; + + if (!name || *name == '\0') + goto cleanup; + + git_buf_printf(&buf, "refs/heads/test:refs/remotes/%s/test", name); + error = git_refspec__parse(&refspec, git_buf_cstr(&buf), true); + + git_buf_free(&buf); + git_refspec__free(&refspec); + +cleanup: + if (error) + giterr_set( + GITERR_CONFIG, + "'%s' is not a valid remote name.", name); + + return error; +} + +static int update_config_refspec( + git_config *config, + const char *remote_name, + const git_refspec *refspec, + int git_direction) +{ + git_buf name = GIT_BUF_INIT, value = GIT_BUF_INIT; + int error = -1; + + if (refspec->src == NULL || refspec->dst == NULL) + return 0; + + if (git_buf_printf( + &name, + "remote.%s.%s", + remote_name, + git_direction == GIT_DIR_FETCH ? "fetch" : "push") < 0) + goto cleanup; + + if (git_refspec__serialize(&value, refspec) < 0) + goto cleanup; + + error = git_config_set_string( + config, + git_buf_cstr(&name), + git_buf_cstr(&value)); + +cleanup: + git_buf_free(&name); + git_buf_free(&value); + + return error; +} + int git_remote_save(const git_remote *remote) { int error; git_config *config; const char *tagopt = NULL; - git_buf buf = GIT_BUF_INIT, value = GIT_BUF_INIT; + git_buf buf = GIT_BUF_INIT; + + assert(remote); + + if (ensure_remote_name_is_valid(remote->name) < 0) + return -1; if (git_repository_config__weakptr(&config, remote->repo) < 0) return -1; @@ -232,6 +306,7 @@ int git_remote_save(const git_remote *remote) int error = git_config_delete(config, git_buf_cstr(&buf)); if (error == GIT_ENOTFOUND) { error = 0; + giterr_clear(); } if (error < 0) { git_buf_free(&buf); @@ -239,33 +314,19 @@ int git_remote_save(const git_remote *remote) } } - if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { - git_buf_clear(&buf); - git_buf_clear(&value); - git_buf_printf(&buf, "remote.%s.fetch", remote->name); - if (remote->fetch.force) - git_buf_putc(&value, '+'); - git_buf_printf(&value, "%s:%s", remote->fetch.src, remote->fetch.dst); - if (git_buf_oom(&buf) || git_buf_oom(&value)) - return -1; - - if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + if (update_config_refspec( + config, + remote->name, + &remote->fetch, + GIT_DIR_FETCH) < 0) goto on_error; - } - - if (remote->push.src != NULL && remote->push.dst != NULL) { - git_buf_clear(&buf); - git_buf_clear(&value); - git_buf_printf(&buf, "remote.%s.push", remote->name); - if (remote->push.force) - git_buf_putc(&value, '+'); - git_buf_printf(&value, "%s:%s", remote->push.src, remote->push.dst); - if (git_buf_oom(&buf) || git_buf_oom(&value)) - return -1; - if (git_config_set_string(config, git_buf_cstr(&buf), git_buf_cstr(&value)) < 0) + if (update_config_refspec( + config, + remote->name, + &remote->push, + GIT_DIR_PUSH) < 0) goto on_error; - } /* * What action to take depends on the old and new values. This @@ -300,13 +361,11 @@ int git_remote_save(const git_remote *remote) } git_buf_free(&buf); - git_buf_free(&value); return 0; on_error: git_buf_free(&buf); - git_buf_free(&value); return -1; } @@ -417,23 +476,30 @@ int git_remote_connect(git_remote *remote, int direction) { git_transport *t; const char *url; + int flags = GIT_TRANSPORTFLAGS_NONE; assert(remote); + t = remote->transport; + url = git_remote__urlfordirection(remote, direction); if (url == NULL ) return -1; - if (git_transport_new(&t, url) < 0) + /* A transport could have been supplied in advance with + * git_remote_set_transport */ + if (!t && git_transport_new(&t, url) < 0) return -1; - t->progress_cb = remote->callbacks.progress; - t->cb_data = remote->callbacks.data; + if (t->set_callbacks && + t->set_callbacks(t, remote->callbacks.progress, NULL, remote->callbacks.data) < 0) + goto on_error; + + if (!remote->check_cert) + flags |= GIT_TRANSPORTFLAGS_NO_CHECK_CERT; - t->check_cert = remote->check_cert; - if (t->connect(t, direction) < 0) { + if (t->connect(t, url, remote->cred_acquire_cb, direction, flags) < 0) goto on_error; - } remote->transport = t; @@ -446,42 +512,151 @@ on_error: int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) { - git_vector *refs = &remote->transport->refs; - unsigned int i; - git_pkt *p = NULL; - assert(remote); - if (!remote->transport || !remote->transport->connected) { + if (!remote->transport) { giterr_set(GITERR_NET, "The remote is not connected"); return -1; } - git_vector_foreach(refs, i, p) { - git_pkt_ref *pkt = NULL; + return remote->transport->ls(remote->transport, list_cb, payload); +} - if (p->type != GIT_PKT_REF) - continue; +int git_remote_download( + git_remote *remote, + git_transfer_progress_callback progress_cb, + void *progress_payload) +{ + int error; - pkt = (git_pkt_ref *)p; + assert(remote); - if (list_cb(&pkt->head, payload)) - return GIT_EUSER; + if ((error = git_fetch_negotiate(remote)) < 0) + return error; + + return git_fetch_download_pack(remote, progress_cb, progress_payload); +} + +static int update_tips_callback(git_remote_head *head, void *payload) +{ + git_vector *refs = (git_vector *)payload; + git_vector_insert(refs, head); + + return 0; +} + +static int remote_head_for_fetchspec_src(git_remote_head **out, git_vector *update_heads, const char *fetchspec_src) +{ + unsigned int i; + git_remote_head *remote_ref; + + assert(update_heads && fetchspec_src); + + *out = NULL; + + git_vector_foreach(update_heads, i, remote_ref) { + if (strcmp(remote_ref->name, fetchspec_src) == 0) { + *out = remote_ref; + break; + } } return 0; } -int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) +static int remote_head_for_ref(git_remote_head **out, git_remote *remote, git_vector *update_heads, git_reference *ref) { - int error; + git_reference *resolved_ref = NULL; + git_reference *tracking_ref = NULL; + git_buf remote_name = GIT_BUF_INIT; + int error = 0; - assert(remote && bytes && stats); + assert(out && remote && ref); - if ((error = git_fetch_negotiate(remote)) < 0) - return error; + *out = NULL; + + if ((error = git_reference_resolve(&resolved_ref, ref)) < 0 || + (!git_reference_is_branch(resolved_ref)) || + (error = git_branch_tracking(&tracking_ref, resolved_ref)) < 0 || + (error = git_refspec_transform_l(&remote_name, &remote->fetch, git_reference_name(tracking_ref))) < 0) { + /* Not an error if HEAD is orphaned or no tracking branch */ + if (error == GIT_ENOTFOUND) + error = 0; + + goto cleanup; + } + + error = remote_head_for_fetchspec_src(out, update_heads, git_buf_cstr(&remote_name)); + +cleanup: + git_reference_free(tracking_ref); + git_reference_free(resolved_ref); + git_buf_free(&remote_name); + return error; +} + +static int git_remote_write_fetchhead(git_remote *remote, git_vector *update_heads) +{ + struct git_refspec *spec; + git_reference *head_ref = NULL; + git_fetchhead_ref *fetchhead_ref; + git_remote_head *remote_ref, *merge_remote_ref; + git_vector fetchhead_refs; + bool include_all_fetchheads; + unsigned int i = 0; + int error = 0; + + assert(remote); + + spec = &remote->fetch; + + if (git_vector_init(&fetchhead_refs, update_heads->length, git_fetchhead_ref_cmp) < 0) + return -1; + + /* Iff refspec is * (but not subdir slash star), include tags */ + include_all_fetchheads = (strcmp(GIT_REFS_HEADS_DIR "*", git_refspec_src(spec)) == 0); + + /* Determine what to merge: if refspec was a wildcard, just use HEAD */ + if (git_refspec_is_wildcard(spec)) { + if ((error = git_reference_lookup(&head_ref, remote->repo, GIT_HEAD_FILE)) < 0 || + (error = remote_head_for_ref(&merge_remote_ref, remote, update_heads, head_ref)) < 0) + goto cleanup; + } else { + /* If we're fetching a single refspec, that's the only thing that should be in FETCH_HEAD. */ + if ((error = remote_head_for_fetchspec_src(&merge_remote_ref, update_heads, git_refspec_src(spec))) < 0) + goto cleanup; + } + + /* Create the FETCH_HEAD file */ + git_vector_foreach(update_heads, i, remote_ref) { + int merge_this_fetchhead = (merge_remote_ref == remote_ref); + + if (!include_all_fetchheads && + !git_refspec_src_matches(spec, remote_ref->name) && + !merge_this_fetchhead) + continue; + + if (git_fetchhead_ref_create(&fetchhead_ref, + &remote_ref->oid, + merge_this_fetchhead, + remote_ref->name, + git_remote_url(remote)) < 0) + goto cleanup; + + if (git_vector_insert(&fetchhead_refs, fetchhead_ref) < 0) + goto cleanup; + } + + git_fetchhead_write(remote->repo, &fetchhead_refs); + +cleanup: + for (i = 0; i < fetchhead_refs.length; ++i) + git_fetchhead_ref_free(fetchhead_refs.contents[i]); + + git_vector_free(&fetchhead_refs); + git_reference_free(head_ref); - return git_fetch_download_pack(remote, bytes, stats); + return error; } int git_remote_update_tips(git_remote *remote) @@ -490,48 +665,48 @@ int git_remote_update_tips(git_remote *remote) unsigned int i = 0; git_buf refname = GIT_BUF_INIT; git_oid old; - git_pkt *pkt; git_odb *odb; - git_vector *refs; git_remote_head *head; git_reference *ref; struct git_refspec *spec; git_refspec tagspec; + git_vector refs, update_heads; assert(remote); - refs = &remote->transport->refs; spec = &remote->fetch; - - if (refs->length == 0) - return 0; - + if (git_repository_odb__weakptr(&odb, remote->repo) < 0) return -1; if (git_refspec__parse(&tagspec, GIT_REFSPEC_TAGS, true) < 0) return -1; - /* HEAD is only allowed to be the first in the list */ - pkt = refs->contents[0]; - head = &((git_pkt_ref *)pkt)->head; - if (!strcmp(head->name, GIT_HEAD_FILE)) { - if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) - return -1; + /* Make a copy of the transport's refs */ + if (git_vector_init(&refs, 16, NULL) < 0 || + git_vector_init(&update_heads, 16, NULL) < 0) + return -1; - i = 1; - git_reference_free(ref); + if (remote->transport->ls(remote->transport, update_tips_callback, &refs) < 0) + goto on_error; + + /* Let's go find HEAD, if it exists. Check only the first ref in the vector. */ + if (refs.length > 0) { + head = (git_remote_head *)refs.contents[0]; + + if (!strcmp(head->name, GIT_HEAD_FILE)) { + if (git_reference_create_oid(&ref, remote->repo, GIT_FETCH_HEAD_FILE, &head->oid, 1) < 0) + goto on_error; + + i = 1; + git_reference_free(ref); + } } - for (; i < refs->length; ++i) { - git_pkt *pkt = refs->contents[i]; + for (; i < refs.length; ++i) { + head = (git_remote_head *)refs.contents[i]; autotag = 0; - if (pkt->type == GIT_PKT_REF) - head = &((git_pkt_ref *)pkt)->head; - else - continue; - /* Ignore malformed ref names (which also saves us from tag^{} */ if (!git_reference_is_valid_name(head->name)) continue; @@ -557,6 +732,9 @@ int git_remote_update_tips(git_remote *remote) if (autotag && !git_odb_exists(odb, &head->oid)) continue; + if (git_vector_insert(&update_heads, head) < 0) + goto on_error; + error = git_reference_name_to_oid(&old, remote->repo, refname.ptr); if (error < 0 && error != GIT_ENOTFOUND) goto on_error; @@ -580,11 +758,19 @@ int git_remote_update_tips(git_remote *remote) } } + if (git_remote_update_fetchhead(remote) && + (error = git_remote_write_fetchhead(remote, &update_heads)) < 0) + goto on_error; + + git_vector_free(&refs); + git_vector_free(&update_heads); git_refspec__free(&tagspec); git_buf_free(&refname); return 0; on_error: + git_vector_free(&refs); + git_vector_free(&update_heads); git_refspec__free(&tagspec); git_buf_free(&refname); return -1; @@ -593,21 +779,31 @@ on_error: int git_remote_connected(git_remote *remote) { + int connected; + assert(remote); - return remote->transport == NULL ? 0 : remote->transport->connected; + + if (!remote->transport || !remote->transport->is_connected) + return 0; + + /* Ask the transport if it's connected. */ + remote->transport->is_connected(remote->transport, &connected); + + return connected; } void git_remote_stop(git_remote *remote) { - git_atomic_set(&remote->transport->cancel, 1); + if (remote->transport->cancel) + remote->transport->cancel(remote->transport); } void git_remote_disconnect(git_remote *remote) { assert(remote); - if (remote->transport != NULL && remote->transport->connected) - remote->transport->close(remote->transport); + if (git_remote_connected(remote)) + remote->transport->close(remote->transport); } void git_remote_free(git_remote *remote) @@ -736,10 +932,39 @@ void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callback memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); + if (remote->transport && remote->transport->set_callbacks) + remote->transport->set_callbacks(remote->transport, + remote->callbacks.progress, + NULL, + remote->callbacks.data); +} + +void git_remote_set_cred_acquire_cb( + git_remote *remote, + git_cred_acquire_cb cred_acquire_cb) +{ + assert(remote); + + remote->cred_acquire_cb = cred_acquire_cb; +} + +int git_remote_set_transport(git_remote *remote, git_transport *transport) +{ + assert(remote && transport); + if (remote->transport) { - remote->transport->progress_cb = remote->callbacks.progress; - remote->transport->cb_data = remote->callbacks.data; + giterr_set(GITERR_NET, "A transport is already bound to this remote"); + return -1; } + + remote->transport = transport; + return 0; +} + +const git_transfer_progress* git_remote_stats(git_remote *remote) +{ + assert(remote); + return &remote->stats; } int git_remote_autotag(git_remote *remote) @@ -751,3 +976,298 @@ void git_remote_set_autotag(git_remote *remote, int value) { remote->download_tags = value; } + +static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) +{ + int error; + git_remote *remote; + + error = git_remote_load(&remote, repo, name); + + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + return error; + + git_remote_free(remote); + + giterr_set( + GITERR_CONFIG, + "Remote '%s' already exists.", name); + + return GIT_EEXISTS; +} + +static int rename_remote_config_section( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + git_buf old_section_name = GIT_BUF_INIT, + new_section_name = GIT_BUF_INIT; + int error = -1; + + if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) + goto cleanup; + + if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) + goto cleanup; + + error = git_config_rename_section( + repo, + git_buf_cstr(&old_section_name), + git_buf_cstr(&new_section_name)); + +cleanup: + git_buf_free(&old_section_name); + git_buf_free(&new_section_name); + + return error; +} + +struct update_data +{ + git_config *config; + const char *old_remote_name; + const char *new_remote_name; +}; + +static int update_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + struct update_data *data = (struct update_data *)payload; + + if (strcmp(entry->value, data->old_remote_name)) + return 0; + + return git_config_set_string( + data->config, + entry->name, + data->new_remote_name); +} + +static int update_branch_remote_config_entry( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + git_config *config; + struct update_data data; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + data.config = config; + data.old_remote_name = old_name; + data.new_remote_name = new_name; + + return git_config_foreach_match( + config, + "branch\\..+\\.remote", + update_config_entries_cb, &data); +} + +static int rename_cb(const char *ref, void *data) +{ + if (git__prefixcmp(ref, GIT_REFS_REMOTES_DIR)) + return 0; + + return git_vector_insert((git_vector *)data, git__strdup(ref)); +} + +static int rename_one_remote_reference( + git_repository *repo, + const char *reference_name, + const char *old_remote_name, + const char *new_remote_name) +{ + int error = -1; + git_buf new_name = GIT_BUF_INIT; + git_reference *reference = NULL; + + if (git_buf_printf( + &new_name, + GIT_REFS_REMOTES_DIR "%s%s", + new_remote_name, + reference_name + strlen(GIT_REFS_REMOTES_DIR) + strlen(old_remote_name)) < 0) + return -1; + + if (git_reference_lookup(&reference, repo, reference_name) < 0) + goto cleanup; + + error = git_reference_rename(reference, git_buf_cstr(&new_name), 0); + +cleanup: + git_reference_free(reference); + git_buf_free(&new_name); + return error; +} + +static int rename_remote_references( + git_repository *repo, + const char *old_name, + const char *new_name) +{ + git_vector refnames; + int error = -1; + unsigned int i; + char *name; + + if (git_vector_init(&refnames, 8, NULL) < 0) + goto cleanup; + + if (git_reference_foreach( + repo, + GIT_REF_LISTALL, + rename_cb, + &refnames) < 0) + goto cleanup; + + git_vector_foreach(&refnames, i, name) { + if ((error = rename_one_remote_reference(repo, name, old_name, new_name)) < 0) + goto cleanup; + } + + error = 0; +cleanup: + git_vector_foreach(&refnames, i, name) { + git__free(name); + } + + git_vector_free(&refnames); + return error; +} + +static int rename_fetch_refspecs( + git_remote *remote, + const char *new_name, + int (*callback)(const char *problematic_refspec, void *payload), + void *payload) +{ + git_config *config; + const git_refspec *fetch_refspec; + git_buf dst_prefix = GIT_BUF_INIT, serialized = GIT_BUF_INIT; + const char* pos; + int error = -1; + + fetch_refspec = git_remote_fetchspec(remote); + + /* Is there a refspec to deal with? */ + if (fetch_refspec->src == NULL && + fetch_refspec->dst == NULL) + return 0; + + if (git_refspec__serialize(&serialized, fetch_refspec) < 0) + goto cleanup; + + /* Is it an in-memory remote? */ + if (remote->name == '\0') { + error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0; + goto cleanup; + } + + if (git_buf_printf(&dst_prefix, ":refs/remotes/%s/", remote->name) < 0) + goto cleanup; + + pos = strstr(git_buf_cstr(&serialized), git_buf_cstr(&dst_prefix)); + + /* Does the dst part of the refspec follow the extected standard format? */ + if (!pos) { + error = (callback(git_buf_cstr(&serialized), payload) < 0) ? GIT_EUSER : 0; + goto cleanup; + } + + if (git_buf_splice( + &serialized, + pos - git_buf_cstr(&serialized) + strlen(":refs/remotes/"), + strlen(remote->name), new_name, + strlen(new_name)) < 0) + goto cleanup; + + git_refspec__free(&remote->fetch); + + if (git_refspec__parse(&remote->fetch, git_buf_cstr(&serialized), true) < 0) + goto cleanup; + + if (git_repository_config__weakptr(&config, remote->repo) < 0) + goto cleanup; + + error = update_config_refspec(config, new_name, &remote->fetch, GIT_DIR_FETCH); + +cleanup: + git_buf_free(&serialized); + git_buf_free(&dst_prefix); + return error; +} + +int git_remote_rename( + git_remote *remote, + const char *new_name, + int (*callback)(const char *problematic_refspec, void *payload), + void *payload) +{ + int error; + + assert(remote && new_name); + + if ((error = ensure_remote_doesnot_exist(remote->repo, new_name)) < 0) + return error; + + if ((error = ensure_remote_name_is_valid(new_name)) < 0) + return error; + + if (!remote->name) { + if ((error = rename_fetch_refspecs( + remote, + new_name, + callback, + payload)) < 0) + return error; + + remote->name = git__strdup(new_name); + + return git_remote_save(remote); + } + + if ((error = rename_remote_config_section( + remote->repo, + remote->name, + new_name)) < 0) + return error; + + if ((error = update_branch_remote_config_entry( + remote->repo, + remote->name, + new_name)) < 0) + return error; + + if ((error = rename_remote_references( + remote->repo, + remote->name, + new_name)) < 0) + return error; + + if ((error = rename_fetch_refspecs( + remote, + new_name, + callback, + payload)) < 0) + return error; + + git__free(remote->name); + remote->name = git__strdup(new_name); + + return 0; +} + +int git_remote_update_fetchhead(git_remote *remote) +{ + return remote->update_fetchhead; +} + +void git_remote_set_update_fetchhead(git_remote *remote, int value) +{ + remote->update_fetchhead = value; +} diff --git a/src/remote.h b/src/remote.h index 05073db8c..840c9a905 100644 --- a/src/remote.h +++ b/src/remote.h @@ -8,9 +8,9 @@ #define INCLUDE_remote_h__ #include "git2/remote.h" +#include "git2/transport.h" #include "refspec.h" -#include "transport.h" #include "repository.h" #define GIT_REMOTE_ORIGIN "origin" @@ -22,12 +22,15 @@ struct git_remote { git_vector refs; struct git_refspec fetch; struct git_refspec push; + git_cred_acquire_cb cred_acquire_cb; git_transport *transport; git_repository *repo; git_remote_callbacks callbacks; + git_transfer_progress stats; unsigned int need_pack:1, download_tags:2, /* There are four possible values */ - check_cert:1; + check_cert:1, + update_fetchhead:1; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/repository.c b/src/repository.c index 43e0eda8f..f82dc108b 100644 --- a/src/repository.c +++ b/src/repository.c @@ -20,6 +20,7 @@ #include "filter.h" #include "odb.h" #include "remote.h" +#include "merge.h" #define GIT_FILE_CONTENT_PREFIX "gitdir:" @@ -449,37 +450,46 @@ static int load_config( const char *xdg_config_path, const char *system_config_path) { + int error; git_buf config_path = GIT_BUF_INIT; git_config *cfg = NULL; assert(repo && out); - if (git_config_new(&cfg) < 0) - return -1; + if ((error = git_config_new(&cfg)) < 0) + return error; - if (git_buf_joinpath( - &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO) < 0) + error = git_buf_joinpath( + &config_path, repo->path_repository, GIT_CONFIG_FILENAME_INREPO); + if (error < 0) goto on_error; - if (git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0) < 0) + if ((error = git_config_add_file_ondisk( + cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, 0)) < 0 && + error != GIT_ENOTFOUND) goto on_error; git_buf_free(&config_path); - if (global_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0) < 0) - goto on_error; - } + if (global_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, global_config_path, GIT_CONFIG_LEVEL_GLOBAL, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; - if (xdg_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0) < 0) - goto on_error; - } + if (xdg_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, xdg_config_path, GIT_CONFIG_LEVEL_XDG, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; - if (system_config_path != NULL) { - if (git_config_add_file_ondisk(cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0) < 0) - goto on_error; - } + if (system_config_path != NULL && + (error = git_config_add_file_ondisk( + cfg, system_config_path, GIT_CONFIG_LEVEL_SYSTEM, 0)) < 0 && + error != GIT_ENOTFOUND) + goto on_error; + + giterr_clear(); /* clear any lingering ENOTFOUND errors */ *out = cfg; return 0; @@ -488,7 +498,7 @@ on_error: git_buf_free(&config_path); git_config_free(cfg); *out = NULL; - return -1; + return error; } int git_repository_config__weakptr(git_config **out, git_repository *repo) @@ -749,6 +759,23 @@ static bool are_symlinks_supported(const char *wd_path) return _symlinks_supported; } +static int create_empty_file(const char *path, mode_t mode) +{ + int fd; + + if ((fd = p_creat(path, mode)) < 0) { + giterr_set(GITERR_OS, "Error while creating '%s'", path); + return -1; + } + + if (p_close(fd) < 0) { + giterr_set(GITERR_OS, "Error while closing '%s'", path); + return -1; + } + + return 0; +} + static int repo_init_config( const char *repo_dir, const char *work_dir, @@ -765,6 +792,12 @@ static int repo_init_config( if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) return -1; + if (!git_path_isfile(git_buf_cstr(&cfg_path)) && + create_empty_file(git_buf_cstr(&cfg_path), GIT_CONFIG_FILE_MODE) < 0) { + git_buf_free(&cfg_path); + return -1; + } + if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { git_buf_free(&cfg_path); return -1; @@ -1206,9 +1239,19 @@ int git_repository_head_detached(git_repository *repo) int git_repository_head(git_reference **head_out, git_repository *repo) { + git_reference *head; int error; - error = git_reference_lookup_resolved(head_out, repo, GIT_HEAD_FILE, -1); + if ((error = git_reference_lookup(&head, repo, GIT_HEAD_FILE)) < 0) + return error; + + if (git_reference_type(head) == GIT_REF_OID) { + *head_out = head; + return 0; + } + + error = git_reference_lookup_resolved(head_out, repo, git_reference_target(head), -1); + git_reference_free(head); return error == GIT_ENOTFOUND ? GIT_EORPHANEDHEAD : error; } @@ -1230,36 +1273,47 @@ int git_repository_head_orphan(git_repository *repo) return 0; } +static int at_least_one_cb(const char *refname, void *payload) +{ + GIT_UNUSED(refname); + GIT_UNUSED(payload); + + return GIT_EUSER; +} + +static int repo_contains_no_reference(git_repository *repo) +{ + int error; + + error = git_reference_foreach(repo, GIT_REF_LISTALL, at_least_one_cb, NULL); + + if (error == GIT_EUSER) + return 0; + + return error == 0 ? 1 : error; +} + int git_repository_is_empty(git_repository *repo) { - git_reference *head = NULL, *branch = NULL; + git_reference *head = NULL; int error; if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) return -1; - if (git_reference_type(head) != GIT_REF_SYMBOLIC) { - git_reference_free(head); - return 0; - } + if (!(error = git_reference_type(head) == GIT_REF_SYMBOLIC)) + goto cleanup; - if (strcmp(git_reference_target(head), GIT_REFS_HEADS_DIR "master") != 0) { - git_reference_free(head); - return 0; - } + if (!(error = strcmp( + git_reference_target(head), + GIT_REFS_HEADS_DIR "master") == 0)) + goto cleanup; - error = git_reference_resolve(&branch, head); + error = repo_contains_no_reference(repo); +cleanup: git_reference_free(head); - git_reference_free(branch); - - if (error == GIT_ENOTFOUND) - return 1; - - if (error < 0) - return -1; - - return 0; + return error < 0 ? -1 : error; } const char *git_repository_path(git_repository *repo) @@ -1348,15 +1402,13 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo) return 0; } -#define MERGE_MSG_FILE "MERGE_MSG" - int git_repository_message(char *buffer, size_t len, git_repository *repo) { git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT; struct stat st; int error; - if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0) + if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) return -1; if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) { @@ -1382,7 +1434,7 @@ int git_repository_message_remove(git_repository *repo) git_buf path = GIT_BUF_INIT; int error; - if (git_buf_joinpath(&path, repo->path_repository, MERGE_MSG_FILE) < 0) + if (git_buf_joinpath(&path, repo->path_repository, GIT_MERGE_MSG_FILE) < 0) return -1; error = p_unlink(git_buf_cstr(&path)); @@ -1541,3 +1593,40 @@ cleanup: git_reference_free(new_head); return error; } + +/** + * Loosely ported from git.git + * https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh#L198-289 + */ +int git_repository_state(git_repository *repo) +{ + git_buf repo_path = GIT_BUF_INIT; + int state = GIT_REPOSITORY_STATE_NONE; + + assert(repo); + + if (git_buf_puts(&repo_path, repo->path_repository) < 0) + return -1; + + if (git_path_contains_file(&repo_path, GIT_REBASE_MERGE_INTERACTIVE_FILE)) + state = GIT_REPOSITORY_STATE_REBASE_INTERACTIVE; + else if (git_path_contains_dir(&repo_path, GIT_REBASE_MERGE_DIR)) + state = GIT_REPOSITORY_STATE_REBASE_MERGE; + else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_REBASING_FILE)) + state = GIT_REPOSITORY_STATE_REBASE; + else if (git_path_contains_file(&repo_path, GIT_REBASE_APPLY_APPLYING_FILE)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX; + else if (git_path_contains_dir(&repo_path, GIT_REBASE_APPLY_DIR)) + state = GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE; + else if (git_path_contains_file(&repo_path, GIT_MERGE_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_MERGE; + else if(git_path_contains_file(&repo_path, GIT_REVERT_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_REVERT; + else if(git_path_contains_file(&repo_path, GIT_CHERRY_PICK_HEAD_FILE)) + state = GIT_REPOSITORY_STATE_CHERRY_PICK; + else if(git_path_contains_file(&repo_path, GIT_BISECT_LOG_FILE)) + state = GIT_REPOSITORY_STATE_BISECT; + + git_buf_free(&repo_path); + return state; +} diff --git a/src/reset.c b/src/reset.c index 560ae17b1..8f470b26a 100644 --- a/src/reset.c +++ b/src/reset.c @@ -8,8 +8,10 @@ #include "common.h" #include "commit.h" #include "tag.h" +#include "merge.h" #include "git2/reset.h" #include "git2/checkout.h" +#include "git2/merge.h" #define ERROR_MSG "Cannot perform reset" @@ -88,6 +90,12 @@ int git_reset( goto cleanup; } + if (reset_type == GIT_RESET_SOFT && (git_repository_state(repo) == GIT_REPOSITORY_STATE_MERGE)) { + giterr_set(GITERR_OBJECT, "%s (soft) while in the middle of a merge.", ERROR_MSG); + error = GIT_EUNMERGED; + goto cleanup; + } + //TODO: Check for unmerged entries if (update_head(repo, commit) < 0) @@ -108,7 +116,7 @@ int git_reset( goto cleanup; } - if (git_index_read_tree(index, tree, NULL) < 0) { + if (git_index_read_tree(index, tree) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG); goto cleanup; } @@ -118,18 +126,20 @@ int git_reset( goto cleanup; } + if ((error = git_merge__cleanup(repo)) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to clean up merge data.", ERROR_MSG); + goto cleanup; + } + if (reset_type == GIT_RESET_MIXED) { error = 0; goto cleanup; } memset(&opts, 0, sizeof(opts)); - opts.checkout_strategy = - GIT_CHECKOUT_CREATE_MISSING - | GIT_CHECKOUT_OVERWRITE_MODIFIED - | GIT_CHECKOUT_REMOVE_UNTRACKED; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; - if (git_checkout_index(repo, &opts, NULL) < 0) { + if (git_checkout_index(repo, NULL, &opts) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); goto cleanup; } diff --git a/src/revparse.c b/src/revparse.c index 83eea7d3f..6b49402c4 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -201,7 +201,7 @@ static int retrieve_previously_checked_out_branch_or_revision(git_object **out, numentries = git_reflog_entrycount(reflog); - for (i = numentries - 1; i >= 0; i--) { + for (i = 0; i < numentries; i++) { entry = git_reflog_entry_byindex(reflog, i); msg = git_reflog_entry_msg(entry); @@ -263,15 +263,15 @@ static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned i } entry = git_reflog_entry_byindex(reflog, identifier); - git_oid_cpy(oid, git_reflog_entry_oidold(entry)); + git_oid_cpy(oid, git_reflog_entry_oidnew(entry)); error = 0; goto cleanup; } else { - int i; + unsigned int i; git_time commit_time; - for (i = numentries - 1; i >= 0; i--) { + for (i = 0; i < numentries; i++) { entry = git_reflog_entry_byindex(reflog, i); commit_time = git_reflog_entry_committer(entry)->when; diff --git a/src/sha1.h b/src/sha1.h deleted file mode 100644 index 41e8abad6..000000000 --- a/src/sha1.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ - -#ifndef INCLUDE_sha1_h__ -#define INCLUDE_sha1_h__ - -#ifdef OPENSSL_SHA -# include <openssl/sha.h> - -#else -typedef struct { - unsigned long long size; - unsigned int H[5]; - unsigned int W[16]; -} blk_SHA_CTX; - - -void git__blk_SHA1_Init(blk_SHA_CTX *ctx); -void git__blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len); -void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); - -#define SHA_CTX blk_SHA_CTX -#define SHA1_Init git__blk_SHA1_Init -#define SHA1_Update git__blk_SHA1_Update -#define SHA1_Final git__blk_SHA1_Final - -#endif // OPENSSL_SHA - -#endif diff --git a/src/stash.c b/src/stash.c new file mode 100644 index 000000000..b74429aca --- /dev/null +++ b/src/stash.c @@ -0,0 +1,660 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "repository.h" +#include "commit.h" +#include "tree.h" +#include "reflog.h" +#include "git2/diff.h" +#include "git2/stash.h" +#include "git2/status.h" +#include "git2/checkout.h" + +static int create_error(int error, const char *msg) +{ + giterr_set(GITERR_STASH, "Cannot stash changes - %s", msg); + return error; +} + +static int ensure_non_bare_repository(git_repository *repo) +{ + if (!git_repository_is_bare(repo)) + return 0; + + return create_error(GIT_EBAREREPO, + "Stash related operations require a working directory."); +} + +static int retrieve_head(git_reference **out, git_repository *repo) +{ + int error = git_repository_head(out, repo); + + if (error == GIT_EORPHANEDHEAD) + return create_error(error, "You do not have the initial commit yet."); + + return error; +} + +static int append_abbreviated_oid(git_buf *out, const git_oid *b_commit) +{ + char *formatted_oid; + + formatted_oid = git_oid_allocfmt(b_commit); + GITERR_CHECK_ALLOC(formatted_oid); + + git_buf_put(out, formatted_oid, 7); + git__free(formatted_oid); + + return git_buf_oom(out) ? -1 : 0; +} + +static int append_commit_description(git_buf *out, git_commit* commit) +{ + const char *message; + int pos = 0, len; + + if (append_abbreviated_oid(out, git_commit_id(commit)) < 0) + return -1; + + message = git_commit_message(commit); + len = strlen(message); + + /* TODO: Replace with proper commit short message + * when git_commit_message_short() is implemented. + */ + while (pos < len && message[pos] != '\n') + pos++; + + git_buf_putc(out, ' '); + git_buf_put(out, message, pos); + git_buf_putc(out, '\n'); + + return git_buf_oom(out) ? -1 : 0; +} + +static int retrieve_base_commit_and_message( + git_commit **b_commit, + git_buf *stash_message, + git_repository *repo) +{ + git_reference *head = NULL; + int error; + + if ((error = retrieve_head(&head, repo)) < 0) + return error; + + error = -1; + + if (strcmp("HEAD", git_reference_name(head)) == 0) + git_buf_puts(stash_message, "(no branch): "); + else + git_buf_printf( + stash_message, + "%s: ", + git_reference_name(head) + strlen(GIT_REFS_HEADS_DIR)); + + if (git_commit_lookup(b_commit, repo, git_reference_oid(head)) < 0) + goto cleanup; + + if (append_commit_description(stash_message, *b_commit) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_reference_free(head); + return error; +} + +static int build_tree_from_index(git_tree **out, git_index *index) +{ + git_oid i_tree_oid; + + if (git_index_write_tree(&i_tree_oid, index) < 0) + return -1; + + return git_tree_lookup(out, git_index_owner(index), &i_tree_oid); +} + +static int commit_index( + git_commit **i_commit, + git_index *index, + git_signature *stasher, + const char *message, + const git_commit *parent) +{ + git_tree *i_tree = NULL; + git_oid i_commit_oid; + git_buf msg = GIT_BUF_INIT; + int error = -1; + + if (build_tree_from_index(&i_tree, index) < 0) + goto cleanup; + + if (git_buf_printf(&msg, "index on %s\n", message) < 0) + goto cleanup; + + if (git_commit_create( + &i_commit_oid, + git_index_owner(index), + NULL, + stasher, + stasher, + NULL, + git_buf_cstr(&msg), + i_tree, + 1, + &parent) < 0) + goto cleanup; + + error = git_commit_lookup(i_commit, git_index_owner(index), &i_commit_oid); + +cleanup: + git_tree_free(i_tree); + git_buf_free(&msg); + return error; +} + +struct cb_data { + git_index *index; + + bool include_changed; + bool include_untracked; + bool include_ignored; +}; + +static int update_index_cb( + void *cb_data, + const git_diff_delta *delta, + float progress) +{ + int pos; + struct cb_data *data = (struct cb_data *)cb_data; + + GIT_UNUSED(progress); + + switch (delta->status) { + case GIT_DELTA_IGNORED: + if (!data->include_ignored) + break; + + return git_index_add_from_workdir(data->index, delta->new_file.path); + + case GIT_DELTA_UNTRACKED: + if (!data->include_untracked) + break; + + return git_index_add_from_workdir(data->index, delta->new_file.path); + + case GIT_DELTA_ADDED: + /* Fall through */ + case GIT_DELTA_MODIFIED: + if (!data->include_changed) + break; + + return git_index_add_from_workdir(data->index, delta->new_file.path); + + case GIT_DELTA_DELETED: + if (!data->include_changed) + break; + + if ((pos = git_index_find(data->index, delta->new_file.path)) < 0) + return -1; + + if (git_index_remove(data->index, delta->new_file.path, 0) < 0) + return -1; + + default: + /* Unimplemented */ + giterr_set( + GITERR_INVALID, + "Cannot update index. Unimplemented status kind (%d)", + delta->status); + return -1; + } + + return 0; +} + +static int build_untracked_tree( + git_tree **tree_out, + git_index *index, + git_commit *i_commit, + uint32_t flags) +{ + git_tree *i_tree = NULL; + git_diff_list *diff = NULL; + git_diff_options opts = {0}; + struct cb_data data = {0}; + int error = -1; + + git_index_clear(index); + + data.index = index; + + if (flags & GIT_STASH_INCLUDE_UNTRACKED) { + opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + data.include_untracked = true; + } + + if (flags & GIT_STASH_INCLUDE_IGNORED) { + opts.flags |= GIT_DIFF_INCLUDE_IGNORED; + data.include_ignored = true; + } + + if (git_commit_tree(&i_tree, i_commit) < 0) + goto cleanup; + + if (git_diff_workdir_to_tree(&diff, git_index_owner(index), i_tree, &opts) < 0) + goto cleanup; + + if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0) + goto cleanup; + + if (build_tree_from_index(tree_out, index) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_diff_list_free(diff); + git_tree_free(i_tree); + return error; +} + +static int commit_untracked( + git_commit **u_commit, + git_index *index, + git_signature *stasher, + const char *message, + git_commit *i_commit, + uint32_t flags) +{ + git_tree *u_tree = NULL; + git_oid u_commit_oid; + git_buf msg = GIT_BUF_INIT; + int error = -1; + + if (build_untracked_tree(&u_tree, index, i_commit, flags) < 0) + goto cleanup; + + if (git_buf_printf(&msg, "untracked files on %s\n", message) < 0) + goto cleanup; + + if (git_commit_create( + &u_commit_oid, + git_index_owner(index), + NULL, + stasher, + stasher, + NULL, + git_buf_cstr(&msg), + u_tree, + 0, + NULL) < 0) + goto cleanup; + + error = git_commit_lookup(u_commit, git_index_owner(index), &u_commit_oid); + +cleanup: + git_tree_free(u_tree); + git_buf_free(&msg); + return error; +} + +static int build_workdir_tree( + git_tree **tree_out, + git_index *index, + git_commit *b_commit) +{ + git_repository *repo = git_index_owner(index); + git_tree *b_tree = NULL; + git_diff_list *diff = NULL, *diff2 = NULL; + git_diff_options opts = {0}; + struct cb_data data = {0}; + int error = -1; + + if (git_commit_tree(&b_tree, b_commit) < 0) + goto cleanup; + + if (git_diff_index_to_tree(&diff, repo, b_tree, NULL, &opts) < 0) + goto cleanup; + + if (git_diff_workdir_to_index(&diff2, repo, NULL, &opts) < 0) + goto cleanup; + + if (git_diff_merge(diff, diff2) < 0) + goto cleanup; + + data.index = index; + data.include_changed = true; + + if (git_diff_foreach(diff, &data, update_index_cb, NULL, NULL) < 0) + goto cleanup; + + if (build_tree_from_index(tree_out, index) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_diff_list_free(diff); + git_diff_list_free(diff2); + git_tree_free(b_tree); + return error; +} + +static int commit_worktree( + git_oid *w_commit_oid, + git_index *index, + git_signature *stasher, + const char *message, + git_commit *i_commit, + git_commit *b_commit, + git_commit *u_commit) +{ + git_tree *w_tree = NULL, *i_tree = NULL; + int error = -1; + + const git_commit *parents[] = { NULL, NULL, NULL }; + + parents[0] = b_commit; + parents[1] = i_commit; + parents[2] = u_commit; + + if (git_commit_tree(&i_tree, i_commit) < 0) + return -1; + + if (git_index_read_tree(index, i_tree) < 0) + goto cleanup; + + if (build_workdir_tree(&w_tree, index, b_commit) < 0) + goto cleanup; + + if (git_commit_create( + w_commit_oid, + git_index_owner(index), + NULL, + stasher, + stasher, + NULL, + message, + w_tree, + u_commit ? 3 : 2, parents) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_tree_free(i_tree); + git_tree_free(w_tree); + return error; +} + +static int prepare_worktree_commit_message( + git_buf* msg, + const char *user_message) +{ + git_buf buf = GIT_BUF_INIT; + int error = -1; + + git_buf_set(&buf, git_buf_cstr(msg), git_buf_len(msg)); + git_buf_clear(msg); + + if (!user_message) + git_buf_printf(msg, "WIP on %s", git_buf_cstr(&buf)); + else { + const char *colon; + + if ((colon = strchr(git_buf_cstr(&buf), ':')) == NULL) + goto cleanup; + + git_buf_puts(msg, "On "); + git_buf_put(msg, git_buf_cstr(&buf), colon - buf.ptr); + git_buf_printf(msg, ": %s\n", user_message); + } + + error = git_buf_oom(msg) || git_buf_oom(&buf) ? -1 : 0; + +cleanup: + git_buf_free(&buf); + return error; +} + +static int update_reflog( + git_oid *w_commit_oid, + git_repository *repo, + git_signature *stasher, + const char *message) +{ + git_reference *stash = NULL; + git_reflog *reflog = NULL; + int error; + + if ((error = git_reference_create_oid(&stash, repo, GIT_REFS_STASH_FILE, w_commit_oid, 1)) < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, stash)) < 0) + goto cleanup; + + if ((error = git_reflog_append(reflog, w_commit_oid, stasher, message)) < 0) + goto cleanup; + + if ((error = git_reflog_write(reflog)) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +static int is_dirty_cb(const char *path, unsigned int status, void *payload) +{ + GIT_UNUSED(path); + GIT_UNUSED(status); + GIT_UNUSED(payload); + + return 1; +} + +static int ensure_there_are_changes_to_stash( + git_repository *repo, + bool include_untracked_files, + bool include_ignored_files) +{ + int error; + git_status_options opts; + + memset(&opts, 0, sizeof(opts)); + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + if (include_untracked_files) + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + + if (include_ignored_files) + opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED; + + error = git_status_foreach_ext(repo, &opts, is_dirty_cb, NULL); + + if (error == GIT_EUSER) + return 0; + + if (!error) + return create_error(GIT_ENOTFOUND, "There is nothing to stash."); + + return error; +} + +static int reset_index_and_workdir( + git_repository *repo, + git_commit *commit, + bool remove_untracked) +{ + git_checkout_opts opts; + + memset(&opts, 0, sizeof(git_checkout_opts)); + + opts.checkout_strategy = + GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED; + + if (remove_untracked) + opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; + + return git_checkout_tree(repo, (git_object *)commit, &opts); +} + +int git_stash_save( + git_oid *out, + git_repository *repo, + git_signature *stasher, + const char *message, + uint32_t flags) +{ + git_index *index = NULL; + git_commit *b_commit = NULL, *i_commit = NULL, *u_commit = NULL; + git_buf msg = GIT_BUF_INIT; + int error; + + assert(out && repo && stasher); + + if ((error = ensure_non_bare_repository(repo)) < 0) + return error; + + if ((error = retrieve_base_commit_and_message(&b_commit, &msg, repo)) < 0) + goto cleanup; + + if ((error = ensure_there_are_changes_to_stash( + repo, + (flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED, + (flags & GIT_STASH_INCLUDE_IGNORED) == GIT_STASH_INCLUDE_IGNORED)) < 0) + goto cleanup; + + error = -1; + + if (git_repository_index(&index, repo) < 0) + goto cleanup; + + if (commit_index(&i_commit, index, stasher, git_buf_cstr(&msg), b_commit) < 0) + goto cleanup; + + if ((flags & GIT_STASH_INCLUDE_UNTRACKED || flags & GIT_STASH_INCLUDE_IGNORED) + && commit_untracked(&u_commit, index, stasher, git_buf_cstr(&msg), i_commit, flags) < 0) + goto cleanup; + + if (prepare_worktree_commit_message(&msg, message) < 0) + goto cleanup; + + if (commit_worktree(out, index, stasher, git_buf_cstr(&msg), i_commit, b_commit, u_commit) < 0) + goto cleanup; + + git_buf_rtrim(&msg); + if (update_reflog(out, repo, stasher, git_buf_cstr(&msg)) < 0) + goto cleanup; + + if (reset_index_and_workdir( + repo, + ((flags & GIT_STASH_KEEP_INDEX) == GIT_STASH_KEEP_INDEX) ? + i_commit : b_commit, + (flags & GIT_STASH_INCLUDE_UNTRACKED) == GIT_STASH_INCLUDE_UNTRACKED) < 0) + goto cleanup; + + error = 0; + +cleanup: + git_buf_free(&msg); + git_commit_free(i_commit); + git_commit_free(b_commit); + git_commit_free(u_commit); + git_index_free(index); + return error; +} + +int git_stash_foreach( + git_repository *repo, + stash_cb callback, + void *payload) +{ + git_reference *stash; + git_reflog *reflog = NULL; + int error; + size_t i, max; + const git_reflog_entry *entry; + + error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE); + if (error == GIT_ENOTFOUND) + return 0; + + if (error < 0) + goto cleanup; + + if ((error = git_reflog_read(&reflog, stash)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + for (i = 0; i < max; i++) { + entry = git_reflog_entry_byindex(reflog, i); + + if (callback(i, + git_reflog_entry_msg(entry), + git_reflog_entry_oidnew(entry), + payload)) { + error = GIT_EUSER; + goto cleanup; + } + } + + error = 0; + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} + +int git_stash_drop( + git_repository *repo, + size_t index) +{ + git_reference *stash; + git_reflog *reflog = NULL; + size_t max; + int error; + + if ((error = git_reference_lookup(&stash, repo, GIT_REFS_STASH_FILE)) < 0) + return error; + + if ((error = git_reflog_read(&reflog, stash)) < 0) + goto cleanup; + + max = git_reflog_entrycount(reflog); + + if (index > max - 1) { + error = GIT_ENOTFOUND; + giterr_set(GITERR_STASH, "No stashed state at position %" PRIuZ, index); + goto cleanup; + } + + if ((error = git_reflog_drop(reflog, index, true)) < 0) + goto cleanup; + + if ((error = git_reflog_write(reflog)) < 0) + goto cleanup; + + if (max == 1) { + error = git_reference_delete(stash); + stash = NULL; + } + +cleanup: + git_reference_free(stash); + git_reflog_free(reflog); + return error; +} diff --git a/src/status.c b/src/status.c index f57100d11..b8c15ef92 100644 --- a/src/status.c +++ b/src/status.c @@ -17,6 +17,7 @@ #include "git2/diff.h" #include "diff.h" +#include "diff_output.h" static unsigned int index_delta2status(git_delta_t index_status) { @@ -76,21 +77,43 @@ static unsigned int workdir_delta2status(git_delta_t workdir_status) return st; } +typedef struct { + int (*cb)(const char *, unsigned int, void *); + void *cbdata; +} status_user_callback; + +static int status_invoke_cb( + void *cbref, git_diff_delta *i2h, git_diff_delta *w2i) +{ + status_user_callback *usercb = cbref; + const char *path = NULL; + unsigned int status = 0; + + if (w2i) { + path = w2i->old_file.path; + status |= workdir_delta2status(w2i->status); + } + if (i2h) { + path = i2h->old_file.path; + status |= index_delta2status(i2h->status); + } + + return usercb->cb(path, status, usercb->cbdata); +} + int git_status_foreach_ext( git_repository *repo, const git_status_options *opts, int (*cb)(const char *, unsigned int, void *), void *cbdata) { - int err = 0, cmp; + int err = 0; git_diff_options diffopt; git_diff_list *idx2head = NULL, *wd2idx = NULL; git_tree *head = NULL; git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - git_diff_delta *i2h, *w2i; - size_t i, j, i_max, j_max; - bool ignore_case = false; + status_user_callback usercb; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); @@ -119,62 +142,26 @@ int git_status_foreach_ext( /* TODO: support EXCLUDE_SUBMODULES flag */ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && - (err = git_diff_index_to_tree(repo, &diffopt, head, &idx2head)) < 0) + (err = git_diff_index_to_tree(&idx2head, repo, head, NULL, &diffopt)) < 0) goto cleanup; if (show != GIT_STATUS_SHOW_INDEX_ONLY && - (err = git_diff_workdir_to_index(repo, &diffopt, &wd2idx)) < 0) + (err = git_diff_workdir_to_index(&wd2idx, repo, NULL, &diffopt)) < 0) goto cleanup; + usercb.cb = cb; + usercb.cbdata = cbdata; + if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { - for (i = 0; !err && i < idx2head->deltas.length; i++) { - i2h = GIT_VECTOR_GET(&idx2head->deltas, i); - if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata)) - err = GIT_EUSER; - } + if ((err = git_diff__paired_foreach( + idx2head, NULL, status_invoke_cb, &usercb)) < 0) + goto cleanup; + git_diff_list_free(idx2head); idx2head = NULL; } - i_max = idx2head ? idx2head->deltas.length : 0; - j_max = wd2idx ? wd2idx->deltas.length : 0; - - if (idx2head && wd2idx && - (0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) || - 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE))) - { - /* Then use the ignore-case sorter... */ - ignore_case = true; - - /* and assert that both are ignore-case sorted. If this function - * ever needs to support merge joining result sets that are not sorted - * by the same function, then it will need to be extended to do a spool - * and sort on one of the results before merge joining */ - assert(0 != (idx2head->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) && - 0 != (wd2idx->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE)); - } - - for (i = 0, j = 0; !err && (i < i_max || j < j_max); ) { - i2h = idx2head ? GIT_VECTOR_GET(&idx2head->deltas,i) : NULL; - w2i = wd2idx ? GIT_VECTOR_GET(&wd2idx->deltas,j) : NULL; - - cmp = !w2i ? -1 : !i2h ? 1 : STRCMP_CASESELECT(ignore_case, i2h->old_file.path, w2i->old_file.path); - - if (cmp < 0) { - if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata)) - err = GIT_EUSER; - i++; - } else if (cmp > 0) { - if (cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata)) - err = GIT_EUSER; - j++; - } else { - if (cb(i2h->old_file.path, index_delta2status(i2h->status) | - workdir_delta2status(w2i->status), cbdata)) - err = GIT_EUSER; - i++; j++; - } - } + err = git_diff__paired_foreach(idx2head, wd2idx, status_invoke_cb, &usercb); cleanup: git_tree_free(head); @@ -217,7 +204,7 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; sfi->status = status; - if (sfi->count > 1 || + if (sfi->count > 1 || (strcmp(sfi->expected, path) != 0 && p_fnmatch(sfi->expected, path, 0) != 0)) { giterr_set(GITERR_INVALID, diff --git a/src/submodule.c b/src/submodule.c index e3657f9ad..6eb1c52f7 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -332,7 +332,7 @@ int git_submodule_add_finalize(git_submodule *sm) assert(sm); if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || - (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0) + (error = git_index_add_from_workdir(index, GIT_MODULES_FILE)) < 0) return error; return git_submodule_add_to_index(sm, true); @@ -371,7 +371,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) memset(&entry, 0, sizeof(entry)); entry.path = sm->path; - git_index__init_entry_from_stat(&st, &entry); + git_index_entry__init_from_stat(&entry, &st); /* calling git_submodule_open will have set sm->wd_oid if possible */ if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { @@ -393,7 +393,7 @@ int git_submodule_add_to_index(git_submodule *sm, int write_index) git_commit_free(head); /* add it */ - error = git_index_add2(index, &entry); + error = git_index_add(index, &entry); /* write it, if requested */ if (!error && write_index) { @@ -733,7 +733,7 @@ int git_submodule_reload(git_submodule *submodule) pos = git_index_find(index, submodule->path); if (pos >= 0) { - git_index_entry *entry = git_index_get(index, pos); + git_index_entry *entry = git_index_get_byindex(index, pos); if (S_ISGITLINK(entry->mode)) { if ((error = submodule_load_from_index(repo, entry)) < 0) @@ -1118,7 +1118,7 @@ static int load_submodule_config_from_index( git_iterator *i; const git_index_entry *entry; - if ((error = git_iterator_for_index(&i, repo)) < 0) + if ((error = git_iterator_for_repo_index(&i, repo)) < 0) return error; error = git_iterator_current(i, &entry); @@ -1455,7 +1455,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) if (sm->ignore == GIT_SUBMODULE_IGNORE_NONE) opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - error = git_diff_index_to_tree(sm_repo, &opt, sm_head, &diff); + error = git_diff_index_to_tree(&diff, sm_repo, sm_head, NULL, &opt); if (!error) { if (git_diff_num_deltas(diff) > 0) @@ -1472,7 +1472,7 @@ static int submodule_wd_status(unsigned int *status, git_submodule *sm) /* perform index-to-workdir diff on submodule */ - error = git_diff_workdir_to_index(sm_repo, &opt, &diff); + error = git_diff_workdir_to_index(&diff, sm_repo, NULL, &opt); if (!error) { size_t untracked = @@ -39,7 +39,7 @@ const git_oid *git_tag_target_oid(git_tag *t) return &t->target; } -git_otype git_tag_type(git_tag *t) +git_otype git_tag_target_type(git_tag *t) { assert(t); return t->type; @@ -139,16 +139,19 @@ int git_tag__parse_buffer(git_tag *tag, const char *buffer, size_t length) return -1; } - if( *buffer != '\n' ) - return tag_error("No new line before message"); + tag->message = NULL; + if (buffer < buffer_end) { + if( *buffer != '\n' ) + return tag_error("No new line before message"); - text_len = buffer_end - ++buffer; + text_len = buffer_end - ++buffer; - tag->message = git__malloc(text_len + 1); - GITERR_CHECK_ALLOC(tag->message); + tag->message = git__malloc(text_len + 1); + GITERR_CHECK_ALLOC(tag->message); - memcpy(tag->message, buffer, text_len); - tag->message[text_len] = '\0'; + memcpy(tag->message, buffer, text_len); + tag->message[text_len] = '\0'; + } return 0; } diff --git a/src/transport.c b/src/transport.c index fb2b94946..8c242af6d 100644 --- a/src/transport.c +++ b/src/transport.c @@ -8,52 +8,78 @@ #include "git2/types.h" #include "git2/remote.h" #include "git2/net.h" -#include "transport.h" +#include "git2/transport.h" #include "path.h" -static struct { +typedef struct transport_definition { char *prefix; + unsigned priority; git_transport_cb fn; -} transports[] = { - {"git://", git_transport_git}, - {"http://", git_transport_http}, - {"https://", git_transport_https}, - {"file://", git_transport_local}, - {"git+ssh://", git_transport_dummy}, - {"ssh+git://", git_transport_dummy}, - {NULL, 0} + void *param; +} transport_definition; + +static transport_definition local_transport_definition = { "file://", 1, git_transport_local, NULL }; +static transport_definition dummy_transport_definition = { NULL, 1, git_transport_dummy, NULL }; + +static git_smart_subtransport_definition http_subtransport_definition = { git_smart_subtransport_http, 1 }; +static git_smart_subtransport_definition git_subtransport_definition = { git_smart_subtransport_git, 0 }; + +static transport_definition transports[] = { + {"git://", 1, git_transport_smart, &git_subtransport_definition}, + {"http://", 1, git_transport_smart, &http_subtransport_definition}, + {"https://", 1, git_transport_smart, &http_subtransport_definition}, + {"file://", 1, git_transport_local, NULL}, + {"git+ssh://", 1, git_transport_dummy, NULL}, + {"ssh+git://", 1, git_transport_dummy, NULL}, + {NULL, 0, 0} }; #define GIT_TRANSPORT_COUNT (sizeof(transports)/sizeof(transports[0])) - 1 -static git_transport_cb transport_find_fn(const char *url) +static int transport_find_fn(const char *url, git_transport_cb *callback, void **param) { size_t i = 0; + unsigned priority = 0; + transport_definition *definition = NULL, *definition_iter; // First, check to see if it's an obvious URL, which a URL scheme for (i = 0; i < GIT_TRANSPORT_COUNT; ++i) { - if (!strncasecmp(url, transports[i].prefix, strlen(transports[i].prefix))) - return transports[i].fn; + definition_iter = &transports[i]; + + if (strncasecmp(url, definition_iter->prefix, strlen(definition_iter->prefix))) + continue; + + if (definition_iter->priority > priority) + definition = definition_iter; } - /* still here? Check to see if the path points to a file on the local file system */ - if ((git_path_exists(url) == 0) && git_path_isdir(url)) - return &git_transport_local; + if (!definition) { + /* still here? Check to see if the path points to a file on the local file system */ + if ((git_path_exists(url) == 0) && git_path_isdir(url)) + definition = &local_transport_definition; + + /* It could be a SSH remote path. Check to see if there's a : */ + if (strrchr(url, ':')) + definition = &dummy_transport_definition; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + } - /* It could be a SSH remote path. Check to see if there's a : */ - if (strrchr(url, ':')) - return &git_transport_dummy; /* SSH is an unsupported transport mechanism in this version of libgit2 */ + if (!definition) + return -1; - return NULL; + *callback = definition->fn; + *param = definition->param; + + return 0; } /************** * Public API * **************/ -int git_transport_dummy(git_transport **transport) +int git_transport_dummy(git_transport **transport, void *param) { GIT_UNUSED(transport); + GIT_UNUSED(param); giterr_set(GITERR_NET, "This transport isn't implemented. Sorry"); return -1; } @@ -62,22 +88,18 @@ int git_transport_new(git_transport **out, const char *url) { git_transport_cb fn; git_transport *transport; + void *param; int error; - fn = transport_find_fn(url); - - if (fn == NULL) { + if (transport_find_fn(url, &fn, ¶m) < 0) { giterr_set(GITERR_NET, "Unsupported URL protocol"); return -1; } - error = fn(&transport); + error = fn(&transport, param); if (error < 0) return error; - transport->url = git__strdup(url); - GITERR_CHECK_ALLOC(transport->url); - *out = transport; return 0; @@ -86,12 +108,19 @@ int git_transport_new(git_transport **out, const char *url) /* from remote.h */ int git_remote_valid_url(const char *url) { - return transport_find_fn(url) != NULL; + git_transport_cb fn; + void *param; + + return !transport_find_fn(url, &fn, ¶m); } int git_remote_supported_url(const char* url) { - git_transport_cb transport_fn = transport_find_fn(url); + git_transport_cb fn; + void *param; + + if (transport_find_fn(url, &fn, ¶m) < 0) + return 0; - return ((transport_fn != NULL) && (transport_fn != &git_transport_dummy)); + return fn != &git_transport_dummy; } diff --git a/src/transport.h b/src/transport.h deleted file mode 100644 index 4c944b9e7..000000000 --- a/src/transport.h +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (C) 2009-2012 the libgit2 contributors - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_transport_h__ -#define INCLUDE_transport_h__ - -#include "git2/net.h" -#include "git2/indexer.h" -#include "vector.h" -#include "posix.h" -#include "common.h" -#include "netops.h" -#ifdef GIT_SSL -# include <openssl/ssl.h> -# include <openssl/err.h> -#endif - - -#define GIT_CAP_OFS_DELTA "ofs-delta" -#define GIT_CAP_MULTI_ACK "multi_ack" -#define GIT_CAP_SIDE_BAND "side-band" -#define GIT_CAP_SIDE_BAND_64K "side-band-64k" -#define GIT_CAP_INCLUDE_TAG "include-tag" - -typedef struct git_transport_caps { - int common:1, - ofs_delta:1, - multi_ack: 1, - side_band:1, - side_band_64k:1, - include_tag:1; -} git_transport_caps; - -#ifdef GIT_SSL -typedef struct gitno_ssl { - SSL_CTX *ctx; - SSL *ssl; -} gitno_ssl; -#endif - - -/* - * A day in the life of a network operation - * ======================================== - * - * The library gets told to ls-remote/push/fetch on/to/from some - * remote. We look at the URL of the remote and fill the function - * table with whatever is appropriate (the remote may be git over git, - * ssh or http(s). It may even be an hg or svn repository, the library - * at this level doesn't care, it just calls the helpers. - * - * The first call is to ->connect() which connects to the remote, - * making use of the direction if necessary. This function must also - * store the remote heads and any other information it needs. - * - * The next useful step is to call ->ls() to get the list of - * references available to the remote. These references may have been - * collected on connect, or we may build them now. For ls-remote, - * nothing else is needed other than closing the connection. - * Otherwise, the higher leves decide which objects we want to - * have. ->send_have() is used to tell the other end what we have. If - * we do need to download a pack, ->download_pack() is called. - * - * When we're done, we call ->close() to close the - * connection. ->free() takes care of freeing all the resources. - */ - -struct git_transport { - /** - * Where the repo lives - */ - char *url; - /** - * Whether we want to push or fetch - */ - int direction : 1, /* 0 fetch, 1 push */ - connected : 1, - check_cert: 1, - use_ssl : 1, - own_logic: 1, /* transitional */ - rpc: 1; /* git-speak for the HTTP transport */ -#ifdef GIT_SSL - struct gitno_ssl ssl; -#endif - git_vector refs; - git_vector common; - gitno_buffer buffer; - GIT_SOCKET socket; - git_transport_caps caps; - void *cb_data; - git_atomic cancel; - - /** - * Connect and store the remote heads - */ - int (*connect)(struct git_transport *transport, int dir); - /** - * Send our side of a negotiation - */ - int (*negotiation_step)(struct git_transport *transport, void *data, size_t len); - /** - * Push the changes over - */ - int (*push)(struct git_transport *transport); - /** - * Negotiate the minimal amount of objects that need to be - * retrieved - */ - int (*negotiate_fetch)(struct git_transport *transport, git_repository *repo, const git_vector *wants); - /** - * Download the packfile - */ - int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); - /** - * Close the connection - */ - int (*close)(struct git_transport *transport); - /** - * Free the associated resources - */ - void (*free)(struct git_transport *transport); - /** - * Callbacks for the progress and error output - */ - void (*progress_cb)(const char *str, int len, void *data); - void (*error_cb)(const char *str, int len, void *data); -}; - - -int git_transport_new(struct git_transport **transport, const char *url); -int git_transport_local(struct git_transport **transport); -int git_transport_git(struct git_transport **transport); -int git_transport_http(struct git_transport **transport); -int git_transport_https(struct git_transport **transport); -int git_transport_dummy(struct git_transport **transport); - -/** - Returns true if the passed URL is valid (a URL with a Git supported scheme, - or pointing to an existing path) -*/ -int git_transport_valid_url(const char *url); - -typedef int (*git_transport_cb)(git_transport **transport); - -#endif diff --git a/src/transports/cred.c b/src/transports/cred.c new file mode 100644 index 000000000..55295372f --- /dev/null +++ b/src/transports/cred.c @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "git2.h" +#include "smart.h" + +static void plaintext_free(struct git_cred *cred) +{ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + int pass_len = strlen(c->password); + + git__free(c->username); + + /* Zero the memory which previously held the password */ + memset(c->password, 0x0, pass_len); + git__free(c->password); + + git__free(c); +} + +int git_cred_userpass_plaintext_new( + git_cred **cred, + const char *username, + const char *password) +{ + git_cred_userpass_plaintext *c; + + if (!cred) + return -1; + + c = (git_cred_userpass_plaintext *)git__malloc(sizeof(git_cred_userpass_plaintext)); + GITERR_CHECK_ALLOC(c); + + c->parent.credtype = GIT_CREDTYPE_USERPASS_PLAINTEXT; + c->parent.free = plaintext_free; + c->username = git__strdup(username); + + if (!c->username) { + git__free(c); + return -1; + } + + c->password = git__strdup(password); + + if (!c->password) { + git__free(c->username); + git__free(c); + return -1; + } + + *cred = &c->parent; + return 0; +}
\ No newline at end of file diff --git a/src/transports/git.c b/src/transports/git.c index b757495c5..a895c1389 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -5,40 +5,37 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/net.h" -#include "git2/common.h" -#include "git2/types.h" -#include "git2/errors.h" -#include "git2/net.h" -#include "git2/revwalk.h" - -#include "vector.h" -#include "transport.h" -#include "pkt.h" -#include "common.h" +#include "git2.h" +#include "buffer.h" #include "netops.h" -#include "filebuf.h" -#include "repository.h" -#include "fetch.h" -#include "protocol.h" + +#define OWNING_SUBTRANSPORT(s) ((git_subtransport *)(s)->parent.subtransport) + +static const char prefix_git[] = "git://"; +static const char cmd_uploadpack[] = "git-upload-pack"; typedef struct { - git_transport parent; - char buff[65536]; -#ifdef GIT_WIN32 - WSADATA wsd; -#endif -} transport_git; + git_smart_subtransport_stream parent; + gitno_socket socket; + const char *cmd; + char *url; + unsigned sent_command : 1; +} git_stream; + +typedef struct { + git_smart_subtransport parent; + git_transport *owner; + git_stream *current_stream; +} git_subtransport; /* - * Create a git procol request. + * Create a git protocol request. * * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 */ static int gen_proto(git_buf *request, const char *cmd, const char *url) { char *delim, *repo; - char default_command[] = "git-upload-pack"; char host[] = "host="; size_t len; @@ -54,9 +51,6 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) if (delim == NULL) delim = strchr(url, '/'); - if (cmd == NULL) - cmd = default_command; - len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; git_buf_grow(request, len); @@ -71,175 +65,211 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) return 0; } -static int send_request(git_transport *t, const char *cmd, const char *url) +static int send_command(git_stream *s) { int error; git_buf request = GIT_BUF_INIT; - error = gen_proto(&request, cmd, url); + error = gen_proto(&request, s->cmd, s->url); if (error < 0) goto cleanup; - error = gitno_send(t, request.ptr, request.size, 0); + /* It looks like negative values are errors here, and positive values + * are the number of bytes sent. */ + error = gitno_send(&s->socket, request.ptr, request.size, 0); + + if (error >= 0) + s->sent_command = 1; cleanup: git_buf_free(&request); return error; } -/* - * Parse the URL and connect to a server, storing the socket in - * out. For convenience this also takes care of asking for the remote - * refs - */ -static int do_connect(transport_git *t, const char *url) +static int git_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) { - char *host, *port; - const char prefix[] = "git://"; + git_stream *s = (git_stream *)stream; + gitno_buffer buf; - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + *bytes_read = 0; - if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + if (!s->sent_command && send_command(s) < 0) return -1; - if (gitno_connect((git_transport *)t, host, port) < 0) - goto on_error; + gitno_buffer_setup(&s->socket, &buf, buffer, buf_size); - if (send_request((git_transport *)t, NULL, url) < 0) - goto on_error; + if (gitno_recv(&buf) < 0) + return -1; - git__free(host); - git__free(port); + *bytes_read = buf.offset; return 0; - -on_error: - git__free(host); - git__free(port); - gitno_close(t->parent.socket); - return -1; } -/* - * Since this is a network connection, we need to parse and store the - * pkt-lines at this stage and keep them there. - */ -static int git_connect(git_transport *transport, int direction) +static int git_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) { - transport_git *t = (transport_git *) transport; + git_stream *s = (git_stream *)stream; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over git:// is not supported"); + if (!s->sent_command && send_command(s) < 0) return -1; + + return gitno_send(&s->socket, buffer, len, 0); +} + +static void git_stream_free(git_smart_subtransport_stream *stream) +{ + git_stream *s = (git_stream *)stream; + git_subtransport *t = OWNING_SUBTRANSPORT(s); + int ret; + + GIT_UNUSED(ret); + + t->current_stream = NULL; + + if (s->socket.socket) { + ret = gitno_close(&s->socket); + assert(!ret); } - t->parent.direction = direction; + git__free(s->url); + git__free(s); +} - /* Connect and ask for the refs */ - if (do_connect(t, transport->url) < 0) +static int git_stream_alloc( + git_subtransport *t, + const char *url, + const char *cmd, + git_smart_subtransport_stream **stream) +{ + git_stream *s; + + if (!stream) return -1; - gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff)); + s = (git_stream *)git__calloc(sizeof(git_stream), 1); + GITERR_CHECK_ALLOC(s); - t->parent.connected = 1; - if (git_protocol_store_refs(transport, 1) < 0) - return -1; + s->parent.subtransport = &t->parent; + s->parent.read = git_stream_read; + s->parent.write = git_stream_write; + s->parent.free = git_stream_free; - if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) + s->cmd = cmd; + s->url = git__strdup(url); + + if (!s->url) { + git__free(s); return -1; + } + *stream = &s->parent; return 0; } -static int git_negotiation_step(struct git_transport *transport, void *data, size_t len) +static int git_git_uploadpack_ls( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) { - return gitno_send(transport, data, len, 0); -} + char *host, *port; + git_stream *s; -static int git_close(git_transport *t) -{ - git_buf buf = GIT_BUF_INIT; + *stream = NULL; - if (git_pkt_buffer_flush(&buf) < 0) - return -1; - /* Can't do anything if there's an error, so don't bother checking */ - gitno_send(t, buf.ptr, buf.size, 0); - git_buf_free(&buf); + if (!git__prefixcmp(url, prefix_git)) + url += strlen(prefix_git); - if (gitno_close(t->socket) < 0) { - giterr_set(GITERR_NET, "Failed to close socket"); + if (git_stream_alloc(t, url, cmd_uploadpack, stream) < 0) return -1; - } - t->connected = 0; + s = (git_stream *)*stream; + + if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) + goto on_error; -#ifdef GIT_WIN32 - WSACleanup(); -#endif + if (gitno_connect(&s->socket, host, port, 0) < 0) + goto on_error; + t->current_stream = s; + git__free(host); + git__free(port); return 0; + +on_error: + if (*stream) + git_stream_free(*stream); + + git__free(host); + git__free(port); + return -1; } -static void git_free(git_transport *transport) +static int git_git_uploadpack( + git_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) { - transport_git *t = (transport_git *) transport; - git_vector *refs = &transport->refs; - unsigned int i; + GIT_UNUSED(url); - for (i = 0; i < refs->length; ++i) { - git_pkt *p = git_vector_get(refs, i); - git_pkt_free(p); + if (t->current_stream) { + *stream = &t->current_stream->parent; + return 0; } - git_vector_free(refs); - refs = &transport->common; - for (i = 0; i < refs->length; ++i) { - git_pkt *p = git_vector_get(refs, i); - git_pkt_free(p); + giterr_set(GITERR_NET, "Must call UPLOADPACK_LS before UPLOADPACK"); + return -1; +} + +static int _git_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) +{ + git_subtransport *t = (git_subtransport *) smart_transport; + + switch (action) { + case GIT_SERVICE_UPLOADPACK_LS: + return git_git_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return git_git_uploadpack(t, url, stream); } - git_vector_free(refs); - git__free(t->parent.url); - git__free(t); + *stream = NULL; + return -1; } -int git_transport_git(git_transport **out) +static void _git_free(git_smart_subtransport *smart_transport) { - transport_git *t; -#ifdef GIT_WIN32 - int ret; -#endif + git_subtransport *t = (git_subtransport *) smart_transport; - t = git__malloc(sizeof(transport_git)); - GITERR_CHECK_ALLOC(t); + assert(!t->current_stream); - memset(t, 0x0, sizeof(transport_git)); - if (git_vector_init(&t->parent.common, 8, NULL)) - goto on_error; + git__free(t); +} - if (git_vector_init(&t->parent.refs, 16, NULL) < 0) - goto on_error; +int git_smart_subtransport_git(git_smart_subtransport **out, git_transport *owner) +{ + git_subtransport *t; - t->parent.connect = git_connect; - t->parent.negotiation_step = git_negotiation_step; - t->parent.close = git_close; - t->parent.free = git_free; + if (!out) + return -1; - *out = (git_transport *) t; + t = (git_subtransport *)git__calloc(sizeof(git_subtransport), 1); + GITERR_CHECK_ALLOC(t); -#ifdef GIT_WIN32 - ret = WSAStartup(MAKEWORD(2,2), &t->wsd); - if (ret != 0) { - git_free(*out); - giterr_set(GITERR_NET, "Winsock init failed"); - return -1; - } -#endif + t->owner = owner; + t->parent.action = _git_action; + t->parent.free = _git_free; + *out = (git_smart_subtransport *) t; return 0; - -on_error: - git__free(t); - return -1; } diff --git a/src/transports/http.c b/src/transports/http.c index 93dd0c326..ba4d8746f 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,28 +4,27 @@ * 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 <stdlib.h> +#ifndef GIT_WINHTTP + #include "git2.h" #include "http_parser.h" - -#include "transport.h" -#include "common.h" -#include "netops.h" #include "buffer.h" -#include "pkt.h" -#include "refs.h" -#include "pack.h" -#include "fetch.h" -#include "filebuf.h" -#include "repository.h" -#include "protocol.h" -#if GIT_WINHTTP -# include <winhttp.h> -# pragma comment(lib, "winhttp.lib") -#endif - -#define WIDEN2(s) L ## s -#define WIDEN(s) WIDEN2(s) +#include "netops.h" +#include "smart.h" + +static const char *prefix_http = "http://"; +static const char *prefix_https = "https://"; +static const char *upload_pack_service = "upload-pack"; +static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; +static const char *upload_pack_service_url = "/git-upload-pack"; +static const char *get_verb = "GET"; +static const char *post_verb = "POST"; +static const char *basic_authtype = "Basic"; + +#define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport) + +#define PARSE_ERROR_GENERIC -1 +#define PARSE_ERROR_REPLAY -2 enum last_cb { NONE, @@ -33,44 +32,93 @@ enum last_cb { VALUE }; +typedef enum { + GIT_HTTP_AUTH_BASIC = 1, +} http_authmechanism_t; + typedef struct { - git_transport parent; - http_parser_settings settings; - git_buf buf; - int error; - int transfer_finished :1, - ct_found :1, - ct_finished :1, - pack_ready :1; - enum last_cb last_cb; - http_parser parser; - char *content_type; - char *path; + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const char *verb; + unsigned sent_request : 1; +} http_stream; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + gitno_socket socket; + const char *path; char *host; char *port; - char *service; - char buffer[65536]; -#ifdef GIT_WIN32 - WSADATA wsd; -#endif -#ifdef GIT_WINHTTP - HINTERNET session; - HINTERNET connection; - HINTERNET request; -#endif -} transport_http; - -static int gen_request(git_buf *buf, const char *path, const char *host, const char *op, - const char *service, ssize_t content_length, int ls) + git_cred *cred; + http_authmechanism_t auth_mechanism; + unsigned connected : 1, + use_ssl : 1; + + /* Parser structures */ + http_parser parser; + http_parser_settings settings; + gitno_buffer parse_buffer; + git_buf parse_header_name; + git_buf parse_header_value; + char parse_buffer_data[2048]; + char *content_type; + git_vector www_authenticate; + enum last_cb last_cb; + int parse_error; + unsigned parse_finished : 1; +} http_subtransport; + +typedef struct { + http_stream *s; + http_subtransport *t; + + /* Target buffer details from read() */ + char *buffer; + size_t buf_size; + size_t *bytes_read; +} parser_context; + +static int apply_basic_credential(git_buf *buf, git_cred *cred) { - if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + git_buf raw = GIT_BUF_INIT; + int error = -1; + + git_buf_printf(&raw, "%s:%s", c->username, c->password); + + if (git_buf_oom(&raw) || + git_buf_puts(buf, "Authorization: Basic ") < 0 || + git_buf_put_base64(buf, git_buf_cstr(&raw), raw.size) < 0 || + git_buf_puts(buf, "\r\n") < 0) + goto on_error; + + error = 0; + +on_error: + if (raw.size) + memset(raw.ptr, 0x0, raw.size); + + git_buf_free(&raw); + return error; +} + +static int gen_request( + git_buf *buf, + const char *path, + const char *host, + git_cred *cred, + http_authmechanism_t auth_mechanism, + const char *op, + const char *service, + const char *service_url, + ssize_t content_length) +{ + if (!path) path = "/"; - if (ls) { - git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service); - } else { - git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service); - } + git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", op, path, service_url); git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n"); git_buf_printf(buf, "Host: %s\r\n", host); if (content_length > 0) { @@ -80,6 +128,13 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c } else { git_buf_puts(buf, "Accept: */*\r\n"); } + + /* Apply credentials to the request */ + if (cred && cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && + auth_mechanism == GIT_HTTP_AUTH_BASIC && + apply_basic_credential(buf, cred) < 0) + return -1; + git_buf_puts(buf, "\r\n"); if (git_buf_oom(buf)) @@ -88,538 +143,508 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) +static int parse_unauthorized_response( + git_vector *www_authenticate, + int *allowed_types, + http_authmechanism_t *auth_mechanism) { -#ifndef GIT_WINHTTP - git_buf request = GIT_BUF_INIT; - const char *verb; - int error = -1; - - verb = ls ? "GET" : "POST"; - /* Generate and send the HTTP request */ - if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); - return -1; + unsigned i; + char *entry; + + git_vector_foreach(www_authenticate, i, entry) { + if (!strncmp(entry, basic_authtype, 5) && + (entry[5] == '\0' || entry[5] == ' ')) { + *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *auth_mechanism = GIT_HTTP_AUTH_BASIC; + } } + return 0; +} - if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) - goto cleanup; +static int on_header_ready(http_subtransport *t) +{ + git_buf *name = &t->parse_header_name; + git_buf *value = &t->parse_header_value; + char *dup; - if (content_length) { - if (gitno_send((git_transport *) t, data, content_length, 0) < 0) - goto cleanup; + if (!t->content_type && !strcmp("Content-Type", git_buf_cstr(name))) { + t->content_type = git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(t->content_type); + } + else if (!strcmp("WWW-Authenticate", git_buf_cstr(name))) { + dup = git__strdup(git_buf_cstr(value)); + GITERR_CHECK_ALLOC(dup); + git_vector_insert(&t->www_authenticate, dup); } - error = 0; + return 0; +} -cleanup: - git_buf_free(&request); - return error; +static int on_header_field(http_parser *parser, const char *str, size_t len) +{ + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + + /* Both parse_header_name and parse_header_value are populated + * and ready for consumption */ + if (VALUE == t->last_cb) + if (on_header_ready(t) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; -#else - wchar_t *verb; - wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH]; + if (NONE == t->last_cb || VALUE == t->last_cb) + git_buf_clear(&t->parse_header_name); + + if (git_buf_put(&t->parse_header_name, str, len) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; + + t->last_cb = FIELD; + return 0; +} + +static int on_header_value(http_parser *parser, const char *str, size_t len) +{ + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + + assert(NONE != t->last_cb); + + if (FIELD == t->last_cb) + git_buf_clear(&t->parse_header_value); + + if (git_buf_put(&t->parse_header_value, str, len) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; + + t->last_cb = VALUE; + return 0; +} + +static int on_headers_complete(http_parser *parser) +{ + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; + http_stream *s = ctx->s; git_buf buf = GIT_BUF_INIT; - BOOL ret; - DWORD flags; - void *buffer; - wchar_t *types[] = { - L"*/*", - NULL, - }; - - verb = ls ? L"GET" : L"POST"; - buffer = data ? data : WINHTTP_NO_REQUEST_DATA; - flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0; - - if (ls) - git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service); - else - git_buf_printf(&buf, "%s/git-%s", t->path, service); - if (git_buf_oom(&buf)) - return -1; + /* Both parse_header_name and parse_header_value are populated + * and ready for consumption. */ + if (VALUE == t->last_cb) + if (on_header_ready(t) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; - git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); + /* Check for an authentication failure. */ + if (parser->status_code == 401 && + get_verb == s->verb && + t->owner->cred_acquire_cb) { + int allowed_types = 0; - t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags); - if (t->request == NULL) { - git_buf_free(&buf); - giterr_set(GITERR_OS, "Failed to open request"); - return -1; - } + if (parse_unauthorized_response(&t->www_authenticate, + &allowed_types, &t->auth_mechanism) < 0) + return t->parse_error = PARSE_ERROR_GENERIC; - git_buf_clear(&buf); - if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) - goto on_error; + if (allowed_types && + (!t->cred || 0 == (t->cred->credtype & allowed_types))) { - git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf)); + if (t->owner->cred_acquire_cb(&t->cred, + t->owner->url, + allowed_types) < 0) + return PARSE_ERROR_GENERIC; - if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) { - giterr_set(GITERR_OS, "Failed to add a header to the request"); - goto on_error; - } + assert(t->cred); - if (!t->parent.check_cert) { - int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA; - if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) { - giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); - goto on_error; + /* Successfully acquired a credential. */ + return t->parse_error = PARSE_ERROR_REPLAY; } } - if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0, - data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) { - giterr_set(GITERR_OS, "Failed to send request"); - goto on_error; + /* Check for a 200 HTTP status code. */ + if (parser->status_code != 200) { + giterr_set(GITERR_NET, + "Unexpected HTTP status code: %d", + parser->status_code); + return t->parse_error = PARSE_ERROR_GENERIC; } - ret = WinHttpReceiveResponse(t->request, NULL); - if (ret == FALSE) { - giterr_set(GITERR_OS, "Failed to receive response"); - goto on_error; + /* The response must contain a Content-Type header. */ + if (!t->content_type) { + giterr_set(GITERR_NET, "No Content-Type header in response"); + return t->parse_error = PARSE_ERROR_GENERIC; } - return 0; + /* The Content-Type header must match our expectation. */ + if (get_verb == s->verb) + git_buf_printf(&buf, + "application/x-git-%s-advertisement", + ctx->s->service); + else + git_buf_printf(&buf, + "application/x-git-%s-result", + ctx->s->service); + + if (git_buf_oom(&buf)) + return t->parse_error = PARSE_ERROR_GENERIC; + + if (strcmp(t->content_type, git_buf_cstr(&buf))) { + git_buf_free(&buf); + giterr_set(GITERR_NET, + "Invalid Content-Type: %s", + t->content_type); + return t->parse_error = PARSE_ERROR_GENERIC; + } -on_error: git_buf_free(&buf); - if (t->request) - WinHttpCloseHandle(t->request); - t->request = NULL; - return -1; -#endif + + return 0; } -static int do_connect(transport_http *t) +static int on_message_complete(http_parser *parser) { -#ifndef GIT_WINHTTP - if (t->parent.connected && http_should_keep_alive(&t->parser)) - return 0; + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; - if (gitno_connect((git_transport *) t, t->host, t->port) < 0) - return -1; - - t->parent.connected = 1; + t->parse_finished = 1; return 0; -#else - wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; - wchar_t host[GIT_WIN_PATH]; - int32_t port; +} - t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); +static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) +{ + parser_context *ctx = (parser_context *) parser->data; + http_subtransport *t = ctx->t; - if (t->session == NULL) { - giterr_set(GITERR_OS, "Failed to init WinHTTP"); - goto on_error; + if (ctx->buf_size < len) { + giterr_set(GITERR_NET, "Can't fit data in the buffer"); + return t->parse_error = PARSE_ERROR_GENERIC; } - git__utf8_to_16(host, GIT_WIN_PATH, t->host); - - if (git__strtol32(&port, t->port, NULL, 10) < 0) - goto on_error; - - t->connection = WinHttpConnect(t->session, host, port, 0); - if (t->connection == NULL) { - giterr_set(GITERR_OS, "Failed to connect to host"); - goto on_error; - } + memcpy(ctx->buffer, str, len); + *(ctx->bytes_read) += len; + ctx->buffer += len; + ctx->buf_size -= len; - t->parent.connected = 1; return 0; - -on_error: - if (t->session) { - WinHttpCloseHandle(t->session); - t->session = NULL; - } - return -1; -#endif } -/* - * The HTTP parser is streaming, so we need to wait until we're in the - * field handler before we can be sure that we can store the previous - * value. Right now, we only care about the - * Content-Type. on_header_{field,value} should be kept generic enough - * to work for any request. - */ +static void clear_parser_state(http_subtransport *t) +{ + unsigned i; + char *entry; -static const char *typestr = "Content-Type"; + http_parser_init(&t->parser, HTTP_RESPONSE); + gitno_buffer_setup(&t->socket, + &t->parse_buffer, + t->parse_buffer_data, + sizeof(t->parse_buffer_data)); -static int on_header_field(http_parser *parser, const char *str, size_t len) -{ - transport_http *t = (transport_http *) parser->data; - git_buf *buf = &t->buf; + t->last_cb = NONE; + t->parse_error = 0; + t->parse_finished = 0; - if (t->last_cb == VALUE && t->ct_found) { - t->ct_finished = 1; - t->ct_found = 0; - t->content_type = git__strdup(git_buf_cstr(buf)); - GITERR_CHECK_ALLOC(t->content_type); - git_buf_clear(buf); - } + git_buf_free(&t->parse_header_name); + git_buf_init(&t->parse_header_name, 0); - if (t->ct_found) { - t->last_cb = FIELD; - return 0; - } + git_buf_free(&t->parse_header_value); + git_buf_init(&t->parse_header_value, 0); - if (t->last_cb != FIELD) - git_buf_clear(buf); + git__free(t->content_type); + t->content_type = NULL; - git_buf_put(buf, str, len); - t->last_cb = FIELD; + git_vector_foreach(&t->www_authenticate, i, entry) + git__free(entry); - return git_buf_oom(buf); + git_vector_free(&t->www_authenticate); } -static int on_header_value(http_parser *parser, const char *str, size_t len) +static int http_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) { - transport_http *t = (transport_http *) parser->data; - git_buf *buf = &t->buf; + http_stream *s = (http_stream *)stream; + http_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf request = GIT_BUF_INIT; + parser_context ctx; - if (t->ct_finished) { - t->last_cb = VALUE; - return 0; - } +replay: + *bytes_read = 0; - if (t->last_cb == VALUE) - git_buf_put(buf, str, len); + assert(t->connected); - if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) { - t->ct_found = 1; - git_buf_clear(buf); - git_buf_put(buf, str, len); - } + if (!s->sent_request) { + clear_parser_state(t); - t->last_cb = VALUE; + if (gen_request(&request, t->path, t->host, + t->cred, t->auth_mechanism, s->verb, + s->service, s->service_url, 0) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } - return git_buf_oom(buf); -} + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } -static int on_headers_complete(http_parser *parser) -{ - transport_http *t = (transport_http *) parser->data; - git_buf *buf = &t->buf; + git_buf_free(&request); + s->sent_request = 1; + } + + t->parse_buffer.offset = 0; - /* The content-type is text/plain for 404, so don't validate */ - if (parser->status_code == 404) { - git_buf_clear(buf); + if (t->parse_finished) return 0; - } - if (t->content_type == NULL) { - t->content_type = git__strdup(git_buf_cstr(buf)); - if (t->content_type == NULL) - return t->error = -1; - } + if (gitno_recv(&t->parse_buffer) < 0) + return -1; - git_buf_clear(buf); - git_buf_printf(buf, "application/x-git-%s-advertisement", t->service); - if (git_buf_oom(buf)) - return t->error = -1; + /* This call to http_parser_execute will result in invocations of the on_* + * family of callbacks. The most interesting of these is + * on_body_fill_buffer, which is called when data is ready to be copied + * into the target buffer. We need to marshal the buffer, buf_size, and + * bytes_read parameters to this callback. */ + ctx.t = t; + ctx.s = s; + ctx.buffer = buffer; + ctx.buf_size = buf_size; + ctx.bytes_read = bytes_read; + + /* Set the context, call the parser, then unset the context. */ + t->parser.data = &ctx; + + http_parser_execute(&t->parser, + &t->settings, + t->parse_buffer.data, + t->parse_buffer.offset); + + t->parser.data = NULL; + + /* If there was a handled authentication failure, then parse_error + * will have signaled us that we should replay the request. */ + if (PARSE_ERROR_REPLAY == t->parse_error) { + s->sent_request = 0; + goto replay; + } - if (strcmp(t->content_type, git_buf_cstr(buf))) - return t->error = -1; + if (t->parse_error < 0) + return -1; - git_buf_clear(buf); return 0; } -static int on_message_complete(http_parser *parser) +static int http_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) { - transport_http *t = (transport_http *) parser->data; + http_stream *s = (http_stream *)stream; + http_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf request = GIT_BUF_INIT; - t->transfer_finished = 1; + assert(t->connected); - if (parser->status_code == 404) { - giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf)); - t->error = -1; - } + /* Since we have to write the Content-Length header up front, we're + * basically limited to a single call to write() per request. */ + assert(!s->sent_request); - return 0; -} + if (!s->sent_request) { + clear_parser_state(t); -static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) -{ - git_transport *transport = (git_transport *) parser->data; - transport_http *t = (transport_http *) parser->data; - gitno_buffer *buf = &transport->buffer; + if (gen_request(&request, t->path, t->host, + t->cred, t->auth_mechanism, s->verb, + s->service, s->service_url, len) < 0) { + giterr_set(GITERR_NET, "Failed to generate request"); + return -1; + } - if (buf->len - buf->offset < len) { - giterr_set(GITERR_NET, "Can't fit data in the buffer"); - return t->error = -1; - } + if (gitno_send(&t->socket, request.ptr, request.size, 0) < 0) + goto on_error; + + if (len && gitno_send(&t->socket, buffer, len, 0) < 0) + goto on_error; - memcpy(buf->data + buf->offset, str, len); - buf->offset += len; + git_buf_free(&request); + s->sent_request = 1; + } return 0; + +on_error: + git_buf_free(&request); + return -1; } -static int http_recv_cb(gitno_buffer *buf) +static void http_stream_free(git_smart_subtransport_stream *stream) { - git_transport *transport = (git_transport *) buf->cb_data; - transport_http *t = (transport_http *) transport; - size_t old_len; - char buffer[2048]; -#ifdef GIT_WINHTTP - DWORD recvd; -#else - gitno_buffer inner; - int error; -#endif - - if (t->transfer_finished) - return 0; + http_stream *s = (http_stream *)stream; -#ifndef GIT_WINHTTP - gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); + git__free(s); +} - if ((error = gitno_recv(&inner)) < 0) - return -1; +static int http_stream_alloc(http_subtransport *t, + git_smart_subtransport_stream **stream) +{ + http_stream *s; - old_len = buf->offset; - http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset); - if (t->error < 0) - return t->error; -#else - old_len = buf->offset; - if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) { - giterr_set(GITERR_OS, "Failed to read data from the network"); - return t->error = -1; - } + if (!stream) + return -1; - if (buf->len - buf->offset < recvd) { - giterr_set(GITERR_NET, "Can't fit data in the buffer"); - return t->error = -1; - } + s = (http_stream *)git__calloc(sizeof(http_stream), 1); + GITERR_CHECK_ALLOC(s); - memcpy(buf->data + buf->offset, buffer, recvd); - buf->offset += recvd; -#endif + s->parent.subtransport = &t->parent; + s->parent.read = http_stream_read; + s->parent.write = http_stream_write; + s->parent.free = http_stream_free; - return (int)(buf->offset - old_len); + *stream = (git_smart_subtransport_stream *)s; + return 0; } -/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */ -static void setup_gitno_buffer(git_transport *transport) +static int http_uploadpack_ls( + http_subtransport *t, + git_smart_subtransport_stream **stream) { - transport_http *t = (transport_http *) transport; + http_stream *s; - /* WinHTTP takes care of this for us */ -#ifndef GIT_WINHTTP - http_parser_init(&t->parser, HTTP_RESPONSE); - t->parser.data = t; - t->transfer_finished = 0; - memset(&t->settings, 0x0, sizeof(http_parser_settings)); - t->settings.on_header_field = on_header_field; - t->settings.on_header_value = on_header_value; - t->settings.on_headers_complete = on_headers_complete; - t->settings.on_body = on_body_fill_buffer; - t->settings.on_message_complete = on_message_complete; -#endif + if (http_stream_alloc(t, stream) < 0) + return -1; + + s = (http_stream *)*stream; - gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t); + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; } -static int http_connect(git_transport *transport, int direction) +static int http_uploadpack( + http_subtransport *t, + git_smart_subtransport_stream **stream) { - transport_http *t = (transport_http *) transport; - int ret; - git_buf request = GIT_BUF_INIT; - const char *service = "upload-pack"; - const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; - const char *default_port; - git_pkt *pkt; + http_stream *s; - if (direction == GIT_DIR_PUSH) { - giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); + if (http_stream_alloc(t, stream) < 0) return -1; - } - - t->parent.direction = direction; - if (!git__prefixcmp(url, prefix_http)) { - url = t->parent.url + strlen(prefix_http); - default_port = "80"; - } + s = (http_stream *)*stream; - if (!git__prefixcmp(url, prefix_https)) { - url += strlen(prefix_https); - default_port = "443"; - } + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; - t->path = strchr(url, '/'); + return 0; +} - if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) - goto cleanup; +static int http_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) +{ + http_subtransport *t = (http_subtransport *)smart_transport; + const char *default_port; + int flags = 0, ret; - t->service = git__strdup(service); - GITERR_CHECK_ALLOC(t->service); + if (!stream) + return -1; - if ((ret = do_connect(t)) < 0) - goto cleanup; + if (!t->host || !t->port || !t->path) { + if (!git__prefixcmp(url, prefix_http)) { + url = url + strlen(prefix_http); + default_port = "80"; + } - if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) - goto cleanup; + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + t->use_ssl = 1; + } - setup_gitno_buffer(transport); - if ((ret = git_protocol_store_refs(transport, 2)) < 0) - goto cleanup; + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, + url, default_port)) < 0) + return ret; - pkt = git_vector_get(&transport->refs, 0); - if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { - giterr_set(GITERR_NET, "Invalid HTTP response"); - return t->error = -1; - } else { - /* Remove the comment pkt from the list */ - git_vector_remove(&transport->refs, 0); - git__free(pkt); + t->path = strchr(url, '/'); } - if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) - return t->error = -1; + if (!t->connected || !http_should_keep_alive(&t->parser)) { + if (t->use_ssl) { + int transport_flags; -cleanup: - git_buf_free(&request); - git_buf_clear(&t->buf); + if (t->owner->parent.read_flags(&t->owner->parent, &transport_flags) < 0) + return -1; - return ret; -} + flags |= GITNO_CONNECT_SSL; -static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) -{ - transport_http *t = (transport_http *) transport; - int ret; + if (GIT_TRANSPORTFLAGS_NO_CHECK_CERT & transport_flags) + flags |= GITNO_CONNECT_SSL_NO_CHECK_CERT; + } - /* First, send the data as a HTTP POST request */ - if ((ret = do_connect(t)) < 0) - return -1; + if (gitno_connect(&t->socket, t->host, t->port, flags) < 0) + return -1; - if (send_request(t, "upload-pack", data, len, 0) < 0) - return -1; - - /* Then we need to set up the buffer to grab data from the HTTP response */ - setup_gitno_buffer(transport); + t->connected = 1; + } - return 0; -} + t->parse_finished = 0; + t->parse_error = 0; -static int http_close(git_transport *transport) -{ -#ifndef GIT_WINHTTP - if (gitno_ssl_teardown(transport) < 0) - return -1; + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + return http_uploadpack_ls(t, stream); - if (gitno_close(transport->socket) < 0) { - giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); - return -1; + case GIT_SERVICE_UPLOADPACK: + return http_uploadpack(t, stream); } -#else - transport_http *t = (transport_http *) transport; - if (t->request) - WinHttpCloseHandle(t->request); - if (t->connection) - WinHttpCloseHandle(t->connection); - if (t->session) - WinHttpCloseHandle(t->session); -#endif + *stream = NULL; + return -1; +} - transport->connected = 0; +static void http_free(git_smart_subtransport *smart_transport) +{ + http_subtransport *t = (http_subtransport *) smart_transport; - return 0; -} + clear_parser_state(t); + if (t->socket.socket) + gitno_close(&t->socket); -static void http_free(git_transport *transport) -{ - transport_http *t = (transport_http *) transport; - git_vector *refs = &transport->refs; - git_vector *common = &transport->common; - unsigned int i; - git_pkt *p; - -#ifdef GIT_WIN32 - /* cleanup the WSA context. note that this context - * can be initialized more than once with WSAStartup(), - * and needs to be cleaned one time for each init call - */ - WSACleanup(); -#endif - - git_vector_foreach(refs, i, p) { - git_pkt_free(p); - } - git_vector_free(refs); - git_vector_foreach(common, i, p) { - git_pkt_free(p); + if (t->cred) { + t->cred->free(t->cred); + t->cred = NULL; } - git_vector_free(common); - git_buf_free(&t->buf); - git__free(t->content_type); + git__free(t->host); git__free(t->port); - git__free(t->service); - git__free(t->parent.url); git__free(t); } -int git_transport_http(git_transport **out) +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) { - transport_http *t; + http_subtransport *t; - t = git__malloc(sizeof(transport_http)); - GITERR_CHECK_ALLOC(t); + if (!out) + return -1; - memset(t, 0x0, sizeof(transport_http)); + t = (http_subtransport *)git__calloc(sizeof(http_subtransport), 1); + GITERR_CHECK_ALLOC(t); - t->parent.connect = http_connect; - t->parent.negotiation_step = http_negotiation_step; - t->parent.close = http_close; + t->owner = (transport_smart *)owner; + t->parent.action = http_action; t->parent.free = http_free; - t->parent.rpc = 1; - - if (git_vector_init(&t->parent.refs, 16, NULL) < 0) { - git__free(t); - return -1; - } -#ifdef GIT_WIN32 - /* on win32, the WSA context needs to be initialized - * before any socket calls can be performed */ - if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) { - http_free((git_transport *) t); - giterr_set(GITERR_OS, "Winsock init failed"); - return -1; - } -#endif + t->settings.on_header_field = on_header_field; + t->settings.on_header_value = on_header_value; + t->settings.on_headers_complete = on_headers_complete; + t->settings.on_body = on_body_fill_buffer; + t->settings.on_message_complete = on_message_complete; - *out = (git_transport *) t; + *out = (git_smart_subtransport *) t; return 0; } -int git_transport_https(git_transport **out) -{ -#if defined(GIT_SSL) || defined(GIT_WINHTTP) - transport_http *t; - if (git_transport_http((git_transport **)&t) < 0) - return -1; - - t->parent.use_ssl = 1; - t->parent.check_cert = 1; - *out = (git_transport *) t; - - return 0; -#else - GIT_UNUSED(out); - - giterr_set(GITERR_NET, "HTTPS support not available"); - return -1; -#endif -} +#endif /* !GIT_WINHTTP */ diff --git a/src/transports/local.c b/src/transports/local.c index 561c84fa0..46c9218c7 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -10,16 +10,29 @@ #include "git2/repository.h" #include "git2/object.h" #include "git2/tag.h" +#include "git2/transport.h" +#include "git2/revwalk.h" +#include "git2/odb_backend.h" +#include "git2/pack.h" +#include "git2/commit.h" +#include "git2/revparse.h" +#include "pack-objects.h" #include "refs.h" -#include "transport.h" #include "posix.h" #include "path.h" #include "buffer.h" -#include "pkt.h" +#include "repository.h" +#include "odb.h" typedef struct { git_transport parent; + char *url; + int direction; + int flags; + git_atomic cancelled; git_repository *repo; + git_vector refs; + unsigned connected : 1; } transport_local; static int add_ref(transport_local *t, const char *name) @@ -27,32 +40,24 @@ static int add_ref(transport_local *t, const char *name) const char peeled[] = "^{}"; git_remote_head *head; git_object *obj = NULL, *target = NULL; - git_transport *transport = (git_transport *) t; git_buf buf = GIT_BUF_INIT; - git_pkt_ref *pkt; - head = git__malloc(sizeof(git_remote_head)); + head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); - pkt = git__malloc(sizeof(git_pkt_ref)); - GITERR_CHECK_ALLOC(pkt); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) { + git__free(head->name); git__free(head); - git__free(pkt->head.name); - git__free(pkt); + return -1; } - pkt->type = GIT_PKT_REF; - memcpy(&pkt->head, head, sizeof(git_remote_head)); - git__free(head); - - if (git_vector_insert(&transport->refs, pkt) < 0) + if (git_vector_insert(&t->refs, head) < 0) { - git__free(pkt->head.name); - git__free(pkt); + git__free(head->name); + git__free(head); return -1; } @@ -60,7 +65,7 @@ static int add_ref(transport_local *t, const char *name) if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) return 0; - if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0) + if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0) return -1; head = NULL; @@ -72,27 +77,21 @@ static int add_ref(transport_local *t, const char *name) } /* And if it's a tag, peel it, and add it to the list */ - head = git__malloc(sizeof(git_remote_head)); + head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); if (git_buf_join(&buf, 0, name, peeled) < 0) return -1; head->name = git_buf_detach(&buf); - pkt = git__malloc(sizeof(git_pkt_ref)); - GITERR_CHECK_ALLOC(pkt); - pkt->type = GIT_PKT_REF; - if (git_tag_peel(&target, (git_tag *) obj) < 0) goto on_error; git_oid_cpy(&head->oid, git_object_id(target)); git_object_free(obj); git_object_free(target); - memcpy(&pkt->head, head, sizeof(git_remote_head)); - git__free(head); - if (git_vector_insert(&transport->refs, pkt) < 0) + if (git_vector_insert(&t->refs, head) < 0) return -1; return 0; @@ -107,12 +106,11 @@ static int store_refs(transport_local *t) { unsigned int i; git_strarray ref_names = {0}; - git_transport *transport = (git_transport *) t; assert(t); if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 || - git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0) + git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0) goto on_error; /* Sort the references first */ @@ -131,7 +129,7 @@ static int store_refs(transport_local *t) return 0; on_error: - git_vector_free(&transport->refs); + git_vector_free(&t->refs); git_strarray_free(&ref_names); return -1; } @@ -140,7 +138,11 @@ on_error: * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. */ -static int local_connect(git_transport *transport, int direction) +static int local_connect( + git_transport *transport, + const char *url, + git_cred_acquire_cb cred_acquire_cb, + int direction, int flags) { git_repository *repo; int error; @@ -148,18 +150,23 @@ static int local_connect(git_transport *transport, int direction) const char *path; git_buf buf = GIT_BUF_INIT; - GIT_UNUSED(direction); + GIT_UNUSED(cred_acquire_cb); + + t->url = git__strdup(url); + GITERR_CHECK_ALLOC(t->url); + t->direction = direction; + t->flags = flags; /* The repo layer doesn't want the prefix */ - if (!git__prefixcmp(transport->url, "file://")) { - if (git_path_fromurl(&buf, transport->url) < 0) { + if (!git__prefixcmp(t->url, "file://")) { + if (git_path_fromurl(&buf, t->url) < 0) { git_buf_free(&buf); return -1; } path = git_buf_cstr(&buf); } else { /* We assume transport->url is already a path */ - path = transport->url; + path = t->url; } error = git_repository_open(&repo, path); @@ -174,26 +181,194 @@ static int local_connect(git_transport *transport, int direction) if (store_refs(t) < 0) return -1; - t->parent.connected = 1; + t->connected = 1; return 0; } -static int local_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) { - GIT_UNUSED(transport); - GIT_UNUSED(repo); - GIT_UNUSED(wants); + transport_local *t = (transport_local *)transport; + unsigned int i; + git_remote_head *head = NULL; - giterr_set(GITERR_NET, "Fetch via local transport isn't implemented. Sorry"); - return -1; + if (!t->connected) { + giterr_set(GITERR_NET, "The transport is not connected"); + return -1; + } + + git_vector_foreach(&t->refs, i, head) { + if (list_cb(head, payload)) + return GIT_EUSER; + } + + return 0; +} + +static int local_negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count) +{ + transport_local *t = (transport_local*)transport; + git_remote_head *rhead; + unsigned int i; + + GIT_UNUSED(refs); + GIT_UNUSED(count); + + /* Fill in the loids */ + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + + int error = git_revparse_single(&obj, repo, rhead->name); + if (!error) + git_oid_cpy(&rhead->loid, git_object_id(obj)); + else if (error != GIT_ENOTFOUND) + return error; + git_object_free(obj); + giterr_clear(); + } + + return 0; +} + +typedef struct foreach_data { + git_transfer_progress *stats; + git_transfer_progress_callback progress_cb; + void *progress_payload; + git_odb_writepack *writepack; +} foreach_data; + +static int foreach_cb(void *buf, size_t len, void *payload) +{ + foreach_data *data = (foreach_data*)payload; + + data->stats->received_bytes += len; + return data->writepack->add(data->writepack, buf, len, data->stats); +} + +static int local_download_pack( + git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload) +{ + transport_local *t = (transport_local*)transport; + git_revwalk *walk = NULL; + git_remote_head *rhead; + unsigned int i; + int error = -1; + git_oid oid; + git_packbuilder *pack = NULL; + git_odb_writepack *writepack = NULL; + git_odb *odb = NULL; + + if ((error = git_revwalk_new(&walk, t->repo)) < 0) + goto cleanup; + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if ((error = git_packbuilder_new(&pack, t->repo)) < 0) + goto cleanup; + + stats->total_objects = 0; + stats->indexed_objects = 0; + stats->received_objects = 0; + stats->received_bytes = 0; + + git_vector_foreach(&t->refs, i, rhead) { + git_object *obj; + if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0) + goto cleanup; + + if (git_object_type(obj) == GIT_OBJ_COMMIT) { + /* Revwalker includes only wanted commits */ + error = git_revwalk_push(walk, &rhead->oid); + if (!git_oid_iszero(&rhead->loid)) + error = git_revwalk_hide(walk, &rhead->loid); + } else { + /* Tag or some other wanted object. Add it on its own */ + error = git_packbuilder_insert(pack, &rhead->oid, rhead->name); + } + git_object_free(obj); + } + + /* Walk the objects, building a packfile */ + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_commit *commit; + + /* Skip commits we already have */ + if (git_odb_exists(odb, &oid)) continue; + + if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) { + const git_oid *tree_oid = git_commit_tree_oid(commit); + git_commit_free(commit); + + /* Add the commit and its tree */ + if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 || + (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) + goto cleanup; + } + } + + if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0) + goto cleanup; + + /* Write the data to the ODB */ + { + foreach_data data = {0}; + data.stats = stats; + data.progress_cb = progress_cb; + data.progress_payload = progress_payload; + data.writepack = writepack; + + if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0) + goto cleanup; + } + error = writepack->commit(writepack, stats); + +cleanup: + if (writepack) writepack->free(writepack); + git_packbuilder_free(pack); + git_revwalk_free(walk); + return error; +} + +static int local_is_connected(git_transport *transport, int *connected) +{ + transport_local *t = (transport_local *)transport; + + *connected = t->connected; + + return 0; +} + +static int local_read_flags(git_transport *transport, int *flags) +{ + transport_local *t = (transport_local *)transport; + + *flags = t->flags; + + return 0; +} + +static void local_cancel(git_transport *transport) +{ + transport_local *t = (transport_local *)transport; + + git_atomic_set(&t->cancelled, 1); } static int local_close(git_transport *transport) { transport_local *t = (transport_local *)transport; - t->parent.connected = 0; + t->connected = 0; git_repository_free(t->repo); t->repo = NULL; @@ -204,18 +379,18 @@ static void local_free(git_transport *transport) { unsigned int i; transport_local *t = (transport_local *) transport; - git_vector *vec = &transport->refs; - git_pkt_ref *pkt; + git_vector *vec = &t->refs; + git_remote_head *head; assert(transport); - git_vector_foreach (vec, i, pkt) { - git__free(pkt->head.name); - git__free(pkt); + git_vector_foreach (vec, i, head) { + git__free(head->name); + git__free(head); } git_vector_free(vec); - git__free(t->parent.url); + git__free(t->url); git__free(t); } @@ -223,20 +398,26 @@ static void local_free(git_transport *transport) * Public API * **************/ -int git_transport_local(git_transport **out) +int git_transport_local(git_transport **out, void *param) { transport_local *t; + GIT_UNUSED(param); + t = git__malloc(sizeof(transport_local)); GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_local)); - - t->parent.own_logic = 1; + t->parent.connect = local_connect; t->parent.negotiate_fetch = local_negotiate_fetch; + t->parent.download_pack = local_download_pack; t->parent.close = local_close; t->parent.free = local_free; + t->parent.ls = local_ls; + t->parent.is_connected = local_is_connected; + t->parent.read_flags = local_read_flags; + t->parent.cancel = local_cancel; *out = (git_transport *) t; diff --git a/src/transports/smart.c b/src/transports/smart.c new file mode 100644 index 000000000..8f9715a3f --- /dev/null +++ b/src/transports/smart.c @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2.h" +#include "smart.h" +#include "refs.h" + +static int git_smart__recv_cb(gitno_buffer *buf) +{ + transport_smart *t = (transport_smart *) buf->cb_data; + size_t old_len, bytes_read; + int error; + + assert(t->current_stream); + + old_len = buf->offset; + + if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0) + return error; + + buf->offset += bytes_read; + + if (t->packetsize_cb) + t->packetsize_cb(bytes_read, t->packetsize_payload); + + return (int)(buf->offset - old_len); +} + +GIT_INLINE(void) git_smart__reset_stream(transport_smart *t) +{ + if (t->current_stream) { + t->current_stream->free(t->current_stream); + t->current_stream = NULL; + } +} + +static int git_smart__set_callbacks( + git_transport *transport, + git_transport_message_cb progress_cb, + git_transport_message_cb error_cb, + void *message_cb_payload) +{ + transport_smart *t = (transport_smart *)transport; + + t->progress_cb = progress_cb; + t->error_cb = error_cb; + t->message_cb_payload = message_cb_payload; + + return 0; +} + +static int git_smart__connect( + git_transport *transport, + const char *url, + git_cred_acquire_cb cred_acquire_cb, + int direction, + int flags) +{ + transport_smart *t = (transport_smart *)transport; + git_smart_subtransport_stream *stream; + int error; + git_pkt *pkt; + + git_smart__reset_stream(t); + + t->url = git__strdup(url); + GITERR_CHECK_ALLOC(t->url); + + t->direction = direction; + t->flags = flags; + t->cred_acquire_cb = cred_acquire_cb; + + if (GIT_DIR_FETCH == direction) + { + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK_LS)) < 0) + return error; + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + /* 2 flushes for RPC; 1 for stateful */ + if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0) + return error; + + /* Strip the comment packet for RPC */ + if (t->rpc) { + pkt = (git_pkt *)git_vector_get(&t->refs, 0); + + if (!pkt || GIT_PKT_COMMENT != pkt->type) { + giterr_set(GITERR_NET, "Invalid response"); + return -1; + } else { + /* Remove the comment pkt from the list */ + git_vector_remove(&t->refs, 0); + git__free(pkt); + } + } + + /* We now have loaded the refs. */ + t->have_refs = 1; + + if (git_smart__detect_caps((git_pkt_ref *)git_vector_get(&t->refs, 0), &t->caps) < 0) + return -1; + + if (t->rpc) + git_smart__reset_stream(t); + + /* We're now logically connected. */ + t->connected = 1; + + return 0; + } + else + { + giterr_set(GITERR_NET, "Push not implemented"); + return -1; + } + + return -1; +} + +static int git_smart__ls(git_transport *transport, git_headlist_cb list_cb, void *payload) +{ + transport_smart *t = (transport_smart *)transport; + unsigned int i; + git_pkt *p = NULL; + + if (!t->have_refs) { + giterr_set(GITERR_NET, "The transport has not yet loaded the refs"); + return -1; + } + + git_vector_foreach(&t->refs, i, p) { + git_pkt_ref *pkt = NULL; + + if (p->type != GIT_PKT_REF) + continue; + + pkt = (git_pkt_ref *)p; + + if (list_cb(&pkt->head, payload)) + return GIT_EUSER; + } + + return 0; +} + +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len) +{ + transport_smart *t = (transport_smart *)transport; + git_smart_subtransport_stream *stream; + int error; + + if (t->rpc) + git_smart__reset_stream(t); + + if (GIT_DIR_FETCH == t->direction) { + if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0) + return error; + + /* If this is a stateful implementation, the stream we get back should be the same */ + assert(t->rpc || t->current_stream == stream); + + /* Save off the current stream (i.e. socket) that we are working with */ + t->current_stream = stream; + + if ((error = stream->write(stream, (const char *)data, len)) < 0) + return error; + + gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t); + + return 0; + } + + giterr_set(GITERR_NET, "Push not implemented"); + return -1; +} + +static void git_smart__cancel(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + + git_atomic_set(&t->cancelled, 1); +} + +static int git_smart__is_connected(git_transport *transport, int *connected) +{ + transport_smart *t = (transport_smart *)transport; + + *connected = t->connected; + + return 0; +} + +static int git_smart__read_flags(git_transport *transport, int *flags) +{ + transport_smart *t = (transport_smart *)transport; + + *flags = t->flags; + + return 0; +} + +static int git_smart__close(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + + git_smart__reset_stream(t); + + t->connected = 0; + + return 0; +} + +static void git_smart__free(git_transport *transport) +{ + transport_smart *t = (transport_smart *)transport; + git_vector *refs = &t->refs; + git_vector *common = &t->common; + unsigned int i; + git_pkt *p; + + /* Make sure that the current stream is closed, if we have one. */ + git_smart__close(transport); + + /* Free the subtransport */ + t->wrapped->free(t->wrapped); + + git_vector_foreach(refs, i, p) { + git_pkt_free(p); + } + git_vector_free(refs); + + git_vector_foreach(common, i, p) { + git_pkt_free(p); + } + git_vector_free(common); + + git__free(t->url); + git__free(t); +} + +int git_transport_smart(git_transport **out, void *param) +{ + transport_smart *t; + git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param; + + if (!param) + return -1; + + t = (transport_smart *)git__calloc(sizeof(transport_smart), 1); + GITERR_CHECK_ALLOC(t); + + t->parent.set_callbacks = git_smart__set_callbacks; + t->parent.connect = git_smart__connect; + t->parent.close = git_smart__close; + t->parent.free = git_smart__free; + t->parent.negotiate_fetch = git_smart__negotiate_fetch; + t->parent.download_pack = git_smart__download_pack; + t->parent.ls = git_smart__ls; + t->parent.is_connected = git_smart__is_connected; + t->parent.read_flags = git_smart__read_flags; + t->parent.cancel = git_smart__cancel; + + t->rpc = definition->rpc; + + if (git_vector_init(&t->refs, 16, NULL) < 0) { + git__free(t); + return -1; + } + + if (definition->callback(&t->wrapped, &t->parent) < 0) { + git__free(t); + return -1; + } + + *out = (git_transport *) t; + return 0; +} diff --git a/src/transports/smart.h b/src/transports/smart.h new file mode 100644 index 000000000..046bc89a4 --- /dev/null +++ b/src/transports/smart.h @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2.h" +#include "vector.h" +#include "netops.h" +#include "buffer.h" + +#define GIT_SIDE_BAND_DATA 1 +#define GIT_SIDE_BAND_PROGRESS 2 +#define GIT_SIDE_BAND_ERROR 3 + +#define GIT_CAP_OFS_DELTA "ofs-delta" +#define GIT_CAP_MULTI_ACK "multi_ack" +#define GIT_CAP_SIDE_BAND "side-band" +#define GIT_CAP_SIDE_BAND_64K "side-band-64k" +#define GIT_CAP_INCLUDE_TAG "include-tag" + +enum git_pkt_type { + GIT_PKT_CMD, + GIT_PKT_FLUSH, + GIT_PKT_REF, + GIT_PKT_HAVE, + GIT_PKT_ACK, + GIT_PKT_NAK, + GIT_PKT_PACK, + GIT_PKT_COMMENT, + GIT_PKT_ERR, + GIT_PKT_DATA, + GIT_PKT_PROGRESS, +}; + +/* Used for multi-ack */ +enum git_ack_status { + GIT_ACK_NONE, + GIT_ACK_CONTINUE, + GIT_ACK_COMMON, + GIT_ACK_READY +}; + +/* This would be a flush pkt */ +typedef struct { + enum git_pkt_type type; +} git_pkt; + +struct git_pkt_cmd { + enum git_pkt_type type; + char *cmd; + char *path; + char *host; +}; + +/* This is a pkt-line with some info in it */ +typedef struct { + enum git_pkt_type type; + git_remote_head head; + char *capabilities; +} git_pkt_ref; + +/* Useful later */ +typedef struct { + enum git_pkt_type type; + git_oid oid; + enum git_ack_status status; +} git_pkt_ack; + +typedef struct { + enum git_pkt_type type; + char comment[GIT_FLEX_ARRAY]; +} git_pkt_comment; + +typedef struct { + enum git_pkt_type type; + int len; + char data[GIT_FLEX_ARRAY]; +} git_pkt_data; + +typedef git_pkt_data git_pkt_progress; + +typedef struct { + enum git_pkt_type type; + char error[GIT_FLEX_ARRAY]; +} git_pkt_err; + +typedef struct transport_smart_caps { + int common:1, + ofs_delta:1, + multi_ack: 1, + side_band:1, + side_band_64k:1, + include_tag:1; +} transport_smart_caps; + +typedef void (*packetsize_cb)(int received, void *payload); + +typedef struct { + git_transport parent; + char *url; + git_cred_acquire_cb cred_acquire_cb; + int direction; + int flags; + git_transport_message_cb progress_cb; + git_transport_message_cb error_cb; + void *message_cb_payload; + git_smart_subtransport *wrapped; + git_smart_subtransport_stream *current_stream; + transport_smart_caps caps; + git_vector refs; + git_vector common; + git_atomic cancelled; + packetsize_cb packetsize_cb; + void *packetsize_payload; + unsigned rpc : 1, + have_refs : 1, + connected : 1; + gitno_buffer buffer; + char buffer_data[65536]; +} transport_smart; + +/* smart_protocol.c */ +int git_smart__store_refs(transport_smart *t, int flushes); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); + +int git_smart__negotiate_fetch( + git_transport *transport, + git_repository *repo, + const git_remote_head * const *refs, + size_t count); + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload); + +/* smart.c */ +int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); + +/* smart_pkt.c */ +int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); +int git_pkt_buffer_flush(git_buf *buf); +int git_pkt_send_flush(GIT_SOCKET s); +int git_pkt_buffer_done(git_buf *buf); +int git_pkt_buffer_wants(const git_remote_head * const *refs, size_t count, transport_smart_caps *caps, git_buf *buf); +int git_pkt_buffer_have(git_oid *oid, git_buf *buf); +void git_pkt_free(git_pkt *pkt); diff --git a/src/pkt.c b/src/transports/smart_pkt.c index 91f9b65c2..26fc0e4aa 100644 --- a/src/pkt.c +++ b/src/transports/smart_pkt.c @@ -12,12 +12,11 @@ #include "git2/refs.h" #include "git2/revwalk.h" -#include "pkt.h" +#include "smart.h" #include "util.h" #include "netops.h" #include "posix.h" #include "buffer.h" -#include "protocol.h" #include <ctype.h> @@ -335,7 +334,7 @@ int git_pkt_buffer_flush(git_buf *buf) return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); } -static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) +static int buffer_want_with_caps(const git_remote_head *head, transport_smart_caps *caps, git_buf *buf) { git_buf str = GIT_BUF_INIT; char oid[GIT_OID_HEXSZ +1] = {0}; @@ -376,28 +375,32 @@ static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps * is overwrite the OID each time. */ -int git_pkt_buffer_wants(const git_vector *refs, git_transport_caps *caps, git_buf *buf) +int git_pkt_buffer_wants( + const git_remote_head * const *refs, + size_t count, + transport_smart_caps *caps, + git_buf *buf) { - unsigned int i = 0; - git_remote_head *head; + size_t i = 0; + const git_remote_head *head; if (caps->common) { - for (; i < refs->length; ++i) { - head = refs->contents[i]; + for (; i < count; ++i) { + head = refs[i]; if (!head->local) break; } - if (buffer_want_with_caps(refs->contents[i], caps, buf) < 0) + if (buffer_want_with_caps(refs[i], caps, buf) < 0) return -1; i++; } - for (; i < refs->length; ++i) { + for (; i < count; ++i) { char oid[GIT_OID_HEXSZ]; - head = refs->contents[i]; + head = refs[i]; if (head->local) continue; diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c new file mode 100644 index 000000000..e24eb2783 --- /dev/null +++ b/src/transports/smart_protocol.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "smart.h" +#include "refs.h" +#include "repository.h" + +#define NETWORK_XFER_THRESHOLD (100*1024) + +int git_smart__store_refs(transport_smart *t, int flushes) +{ + gitno_buffer *buf = &t->buffer; + git_vector *refs = &t->refs; + int error, flush = 0, recvd; + const char *line_end; + git_pkt *pkt; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); + else + error = GIT_EBUFS; + + if (error < 0 && error != GIT_EBUFS) + return -1; + + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return -1; + + if (recvd == 0 && !flush) { + giterr_set(GITERR_NET, "Early EOF"); + return -1; + } + + continue; + } + + gitno_consume(buf, line_end); + if (pkt->type == GIT_PKT_ERR) { + giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); + git__free(pkt); + return -1; + } + + if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) + return -1; + + if (pkt->type == GIT_PKT_FLUSH) { + flush++; + git_pkt_free(pkt); + } + } while (flush < flushes); + + return flush; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) +{ + const char *ptr; + + /* No refs or capabilites, odd but not a problem */ + if (pkt == NULL || pkt->capabilities == NULL) + return 0; + + ptr = pkt->capabilities; + while (ptr != NULL && *ptr != '\0') { + if (*ptr == ' ') + ptr++; + + if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { + caps->common = caps->ofs_delta = 1; + ptr += strlen(GIT_CAP_OFS_DELTA); + continue; + } + + if(!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { + caps->common = caps->multi_ack = 1; + ptr += strlen(GIT_CAP_MULTI_ACK); + continue; + } + + if(!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { + caps->common = caps->include_tag = 1; + ptr += strlen(GIT_CAP_INCLUDE_TAG); + continue; + } + + /* Keep side-band check after side-band-64k */ + if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { + caps->common = caps->side_band_64k = 1; + ptr += strlen(GIT_CAP_SIDE_BAND_64K); + continue; + } + + if(!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { + caps->common = caps->side_band = 1; + ptr += strlen(GIT_CAP_SIDE_BAND); + continue; + } + + /* We don't know this capability, so skip it */ + ptr = strchr(ptr, ' '); + } + + return 0; +} + +static int recv_pkt(git_pkt **out, gitno_buffer *buf) +{ + const char *ptr = buf->data, *line_end = ptr; + git_pkt *pkt; + int pkt_type, error = 0, ret; + + do { + if (buf->offset > 0) + error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); + else + error = GIT_EBUFS; + + if (error == 0) + break; /* return the pkt */ + + if (error < 0 && error != GIT_EBUFS) + return -1; + + if ((ret = gitno_recv(buf)) < 0) + return -1; + } while (error); + + gitno_consume(buf, line_end); + pkt_type = pkt->type; + if (out != NULL) + *out = pkt; + else + git__free(pkt); + + return pkt_type; +} + +static int store_common(transport_smart *t) +{ + git_pkt *pkt = NULL; + gitno_buffer *buf = &t->buffer; + + do { + if (recv_pkt(&pkt, buf) < 0) + return -1; + + if (pkt->type == GIT_PKT_ACK) { + if (git_vector_insert(&t->common, pkt) < 0) + return -1; + } else { + git__free(pkt); + return 0; + } + + } while (1); + + return 0; +} + +static int fetch_setup_walk(git_revwalk **out, git_repository *repo) +{ + git_revwalk *walk; + git_strarray refs; + unsigned int i; + git_reference *ref; + + if (git_reference_list(&refs, repo, GIT_REF_LISTALL) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + return -1; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + for (i = 0; i < refs.count; ++i) { + /* No tags */ + if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) + continue; + + if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) + goto on_error; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) + continue; + if (git_revwalk_push(walk, git_reference_oid(ref)) < 0) + goto on_error; + + git_reference_free(ref); + } + + git_strarray_free(&refs); + *out = walk; + return 0; + +on_error: + git_reference_free(ref); + git_strarray_free(&refs); + return -1; +} + +int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count) +{ + transport_smart *t = (transport_smart *)transport; + gitno_buffer *buf = &t->buffer; + git_buf data = GIT_BUF_INIT; + git_revwalk *walk = NULL; + int error = -1, pkt_type; + unsigned int i; + git_oid oid; + + /* No own logic, do our thing */ + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + return -1; + + if (fetch_setup_walk(&walk, repo) < 0) + goto on_error; + /* + * We don't support any kind of ACK extensions, so the negotiation + * boils down to sending what we have and listening for an ACK + * every once in a while. + */ + i = 0; + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + git_pkt_buffer_flush(&data); + if (git_buf_oom(&data)) + goto on_error; + + if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0) + goto on_error; + + git_buf_clear(&data); + if (t->caps.multi_ack) { + if (store_common(t) < 0) + goto on_error; + } else { + pkt_type = recv_pkt(NULL, buf); + + if (pkt_type == GIT_PKT_ACK) { + break; + } else if (pkt_type == GIT_PKT_NAK) { + continue; + } else { + giterr_set(GITERR_NET, "Unexpected pkt type"); + goto on_error; + } + } + } + + if (t->common.length > 0) + break; + + if (i % 20 == 0 && t->rpc) { + git_pkt_ack *pkt; + unsigned int i; + + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + goto on_error; + + git_vector_foreach(&t->common, i, pkt) { + git_pkt_buffer_have(&pkt->oid, &data); + } + + if (git_buf_oom(&data)) + goto on_error; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto on_error; + + /* Tell the other end that we're done negotiating */ + if (t->rpc && t->common.length > 0) { + git_pkt_ack *pkt; + unsigned int i; + + if (git_pkt_buffer_wants(refs, count, &t->caps, &data) < 0) + goto on_error; + + git_vector_foreach(&t->common, i, pkt) { + git_pkt_buffer_have(&pkt->oid, &data); + } + + if (git_buf_oom(&data)) + goto on_error; + } + + git_pkt_buffer_done(&data); + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + if (git_smart__negotiation_step(&t->parent, data.ptr, data.size) < 0) + goto on_error; + + git_buf_free(&data); + git_revwalk_free(walk); + + /* Now let's eat up whatever the server gives us */ + if (!t->caps.multi_ack) { + pkt_type = recv_pkt(NULL, buf); + if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { + giterr_set(GITERR_NET, "Unexpected pkt type"); + return -1; + } + } else { + git_pkt_ack *pkt; + do { + if (recv_pkt((git_pkt **)&pkt, buf) < 0) + return -1; + + if (pkt->type == GIT_PKT_NAK || + (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { + git__free(pkt); + break; + } + + git__free(pkt); + } while (1); + } + + return 0; + +on_error: + git_revwalk_free(walk); + git_buf_free(&data); + return error; +} + +static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats) +{ + int recvd; + + do { + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + return GIT_EUSER; + } + + if (writepack->add(writepack, buf->data, buf->offset, stats) < 0) + return -1; + + gitno_consume_n(buf, buf->offset); + + if ((recvd = gitno_recv(buf)) < 0) + return -1; + } while(recvd > 0); + + if (writepack->commit(writepack, stats)) + return -1; + + return 0; +} + +struct network_packetsize_payload +{ + git_transfer_progress_callback callback; + void *payload; + git_transfer_progress *stats; + size_t last_fired_bytes; +}; + +static void network_packetsize(int received, void *payload) +{ + struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; + + /* Accumulate bytes */ + npp->stats->received_bytes += received; + + /* Fire notification if the threshold is reached */ + if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { + npp->last_fired_bytes = npp->stats->received_bytes; + npp->callback(npp->stats, npp->payload); + } +} + +int git_smart__download_pack( + git_transport *transport, + git_repository *repo, + git_transfer_progress *stats, + git_transfer_progress_callback progress_cb, + void *progress_payload) +{ + transport_smart *t = (transport_smart *)transport; + gitno_buffer *buf = &t->buffer; + git_odb *odb; + struct git_odb_writepack *writepack = NULL; + int error = -1; + struct network_packetsize_payload npp = {0}; + + memset(stats, 0, sizeof(git_transfer_progress)); + + if (progress_cb) { + npp.callback = progress_cb; + npp.payload = progress_payload; + npp.stats = stats; + t->packetsize_cb = &network_packetsize; + t->packetsize_payload = &npp; + + /* We might have something in the buffer already from negotiate_fetch */ + if (t->buffer.offset > 0) + t->packetsize_cb(t->buffer.offset, t->packetsize_payload); + } + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || + ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)) + goto on_error; + + /* + * If the remote doesn't support the side-band, we can feed + * the data directly to the pack writer. Otherwise, we need to + * check which one belongs there. + */ + if (!t->caps.side_band && !t->caps.side_band_64k) { + if (no_sideband(t, writepack, buf, stats) < 0) + goto on_error; + + goto on_success; + } + + do { + git_pkt *pkt; + + if (t->cancelled.val) { + giterr_set(GITERR_NET, "The fetch was cancelled by the user"); + error = GIT_EUSER; + goto on_error; + } + + if (recv_pkt(&pkt, buf) < 0) + goto on_error; + + if (pkt->type == GIT_PKT_PROGRESS) { + if (t->progress_cb) { + git_pkt_progress *p = (git_pkt_progress *) pkt; + t->progress_cb(p->data, p->len, t->message_cb_payload); + } + git__free(pkt); + } else if (pkt->type == GIT_PKT_DATA) { + git_pkt_data *p = (git_pkt_data *) pkt; + if (writepack->add(writepack, p->data, p->len, stats) < 0) + goto on_error; + + git__free(pkt); + } else if (pkt->type == GIT_PKT_FLUSH) { + /* A flush indicates the end of the packfile */ + git__free(pkt); + break; + } + } while (1); + + if (writepack->commit(writepack, stats) < 0) + goto on_error; + +on_success: + error = 0; + +on_error: + writepack->free(writepack); + + /* Trailing execution of progress_cb, if necessary */ + if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) + npp.callback(npp.stats, npp.payload); + + return error; +} diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c new file mode 100644 index 000000000..df6cd87ec --- /dev/null +++ b/src/transports/winhttp.c @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifdef GIT_WINHTTP + +#include "git2.h" +#include "git2/transport.h" +#include "buffer.h" +#include "posix.h" +#include "netops.h" +#include "smart.h" + +#include <winhttp.h> +#pragma comment(lib, "winhttp") + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) + +#define MAX_CONTENT_TYPE_LEN 100 +#define WINHTTP_OPTION_PEERDIST_EXTENSION_STATE 109 + +static const char *prefix_http = "http://"; +static const char *prefix_https = "https://"; +static const char *upload_pack_service = "upload-pack"; +static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack"; +static const char *upload_pack_service_url = "/git-upload-pack"; +static const wchar_t *get_verb = L"GET"; +static const wchar_t *post_verb = L"POST"; +static const wchar_t *basic_authtype = L"Basic"; +static const wchar_t *pragma_nocache = L"Pragma: no-cache"; +static const int no_check_cert_flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | + SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | + SECURITY_FLAG_IGNORE_UNKNOWN_CA; + +#define OWNING_SUBTRANSPORT(s) ((winhttp_subtransport *)(s)->parent.subtransport) + +typedef enum { + GIT_WINHTTP_AUTH_BASIC = 1, +} winhttp_authmechanism_t; + +typedef struct { + git_smart_subtransport_stream parent; + const char *service; + const char *service_url; + const wchar_t *verb; + HINTERNET request; + unsigned sent_request : 1, + received_response : 1; +} winhttp_stream; + +typedef struct { + git_smart_subtransport parent; + transport_smart *owner; + const char *path; + char *host; + char *port; + git_cred *cred; + int auth_mechanism; + HINTERNET session; + HINTERNET connection; + unsigned use_ssl : 1; +} winhttp_subtransport; + +static int apply_basic_credential(HINTERNET request, git_cred *cred) +{ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + git_buf buf = GIT_BUF_INIT, raw = GIT_BUF_INIT; + wchar_t *wide = NULL; + int error = -1, wide_len; + + git_buf_printf(&raw, "%s:%s", c->username, c->password); + + if (git_buf_oom(&raw) || + git_buf_puts(&buf, "Authorization: Basic ") < 0 || + git_buf_put_base64(&buf, git_buf_cstr(&raw), raw.size) < 0) + goto on_error; + + wide_len = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, NULL, 0); + + if (!wide_len) { + giterr_set(GITERR_OS, "Failed to measure string for wide conversion"); + goto on_error; + } + + wide = (wchar_t *)git__malloc(wide_len * sizeof(wchar_t)); + + if (!wide) + goto on_error; + + if (!MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, + git_buf_cstr(&buf), -1, wide, wide_len)) { + giterr_set(GITERR_OS, "Failed to convert string to wide form"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(request, wide, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + error = 0; + +on_error: + /* We were dealing with plaintext passwords, so clean up after ourselves a bit. */ + if (wide) + memset(wide, 0x0, wide_len * sizeof(wchar_t)); + + if (buf.size) + memset(buf.ptr, 0x0, buf.size); + + if (raw.size) + memset(raw.ptr, 0x0, raw.size); + + git__free(wide); + git_buf_free(&buf); + git_buf_free(&raw); + return error; +} + +static int winhttp_stream_connect(winhttp_stream *s) +{ + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + git_buf buf = GIT_BUF_INIT; + wchar_t url[GIT_WIN_PATH], ct[MAX_CONTENT_TYPE_LEN]; + wchar_t *types[] = { L"*/*", NULL }; + BOOL peerdist = FALSE; + + /* Prepare URL */ + git_buf_printf(&buf, "%s%s", t->path, s->service_url); + + if (git_buf_oom(&buf)) + return -1; + + git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); + + /* Establish request */ + s->request = WinHttpOpenRequest( + t->connection, + s->verb, + url, + NULL, + WINHTTP_NO_REFERER, + types, + t->use_ssl ? WINHTTP_FLAG_SECURE : 0); + + if (!s->request) { + giterr_set(GITERR_OS, "Failed to open request"); + goto on_error; + } + + /* Strip unwanted headers (X-P2P-PeerDist, X-P2P-PeerDistEx) that WinHTTP + * adds itself. This option may not be supported by the underlying + * platform, so we do not error-check it */ + WinHttpSetOption(s->request, + WINHTTP_OPTION_PEERDIST_EXTENSION_STATE, + &peerdist, + sizeof(peerdist)); + + /* Send Pragma: no-cache header */ + if (!WinHttpAddRequestHeaders(s->request, pragma_nocache, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + + /* Send Content-Type header -- only necessary on a POST */ + if (post_verb == s->verb) { + git_buf_clear(&buf); + if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", s->service) < 0) + goto on_error; + + git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)); + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + } + + /* If requested, disable certificate validation */ + if (t->use_ssl) { + int flags; + + if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) + goto on_error; + + if ((GIT_TRANSPORTFLAGS_NO_CHECK_CERT & flags) && + !WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, + (LPVOID)&no_check_cert_flags, sizeof(no_check_cert_flags))) { + giterr_set(GITERR_OS, "Failed to set options to ignore cert errors"); + goto on_error; + } + } + + /* If we have a credential on the subtransport, apply it to the request */ + if (t->cred && + t->cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT && + t->auth_mechanism == GIT_WINHTTP_AUTH_BASIC && + apply_basic_credential(s->request, t->cred) < 0) + goto on_error; + + /* We've done everything up to calling WinHttpSendRequest. */ + + return 0; + +on_error: + git_buf_free(&buf); + return -1; +} + +static int parse_unauthorized_response( + HINTERNET request, + int *allowed_types, + int *auth_mechanism) +{ + DWORD index, buf_size, last_error; + int error = 0; + wchar_t *buf = NULL; + + *allowed_types = 0; + + for (index = 0; ; index++) { + /* Make a first call to ask for the size of the buffer to allocate + * to hold the WWW-Authenticate header */ + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, + WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_OUTPUT_BUFFER, + &buf_size, &index)) + { + last_error = GetLastError(); + + if (ERROR_WINHTTP_HEADER_NOT_FOUND == last_error) { + /* End of enumeration */ + break; + } else if (ERROR_INSUFFICIENT_BUFFER == last_error) { + git__free(buf); + buf = (wchar_t *)git__malloc(buf_size); + + if (!buf) { + error = -1; + break; + } + } else { + giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); + error = -1; + break; + } + } + + /* Actually receive the data into our now-allocated buffer */ + if (!WinHttpQueryHeaders(request, WINHTTP_QUERY_WWW_AUTHENTICATE, + WINHTTP_HEADER_NAME_BY_INDEX, buf, + &buf_size, &index)) { + giterr_set(GITERR_OS, "Failed to read WWW-Authenticate header"); + error = -1; + break; + } + + if (!wcsncmp(buf, basic_authtype, 5) && + (buf[5] == L'\0' || buf[5] == L' ')) { + *allowed_types |= GIT_CREDTYPE_USERPASS_PLAINTEXT; + *auth_mechanism = GIT_WINHTTP_AUTH_BASIC; + } + } + + git__free(buf); + return error; +} + +static int winhttp_stream_read( + git_smart_subtransport_stream *stream, + char *buffer, + size_t buf_size, + size_t *bytes_read) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD dw_bytes_read; + +replay: + /* Connect if necessary */ + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + if (!s->sent_request && + !WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + 0, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + + if (!s->received_response) { + DWORD status_code, status_code_length, content_type_length; + char expected_content_type_8[MAX_CONTENT_TYPE_LEN]; + wchar_t expected_content_type[MAX_CONTENT_TYPE_LEN], content_type[MAX_CONTENT_TYPE_LEN]; + + if (!WinHttpReceiveResponse(s->request, 0)) { + giterr_set(GITERR_OS, "Failed to receive response"); + return -1; + } + + /* Verify that we got a 200 back */ + status_code_length = sizeof(status_code); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &status_code, &status_code_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to retreive status code"); + return -1; + } + + /* Handle authentication failures */ + if (HTTP_STATUS_DENIED == status_code && + get_verb == s->verb && t->owner->cred_acquire_cb) { + int allowed_types; + + if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0) + return -1; + + if (allowed_types && + (!t->cred || 0 == (t->cred->credtype & allowed_types))) { + + if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types) < 0) + return -1; + + assert(t->cred); + + WinHttpCloseHandle(s->request); + s->request = NULL; + s->sent_request = 0; + + /* Successfully acquired a credential */ + goto replay; + } + } + + if (HTTP_STATUS_OK != status_code) { + giterr_set(GITERR_NET, "Request failed with status code: %d", status_code); + return -1; + } + + /* Verify that we got the correct content-type back */ + if (post_verb == s->verb) + snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-result", s->service); + else + snprintf(expected_content_type_8, MAX_CONTENT_TYPE_LEN, "application/x-git-%s-advertisement", s->service); + + git__utf8_to_16(expected_content_type, MAX_CONTENT_TYPE_LEN, expected_content_type_8); + content_type_length = sizeof(content_type); + + if (!WinHttpQueryHeaders(s->request, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + &content_type, &content_type_length, + WINHTTP_NO_HEADER_INDEX)) { + giterr_set(GITERR_OS, "Failed to retrieve response content-type"); + return -1; + } + + if (wcscmp(expected_content_type, content_type)) { + giterr_set(GITERR_NET, "Received unexpected content-type"); + return -1; + } + + s->received_response = 1; + } + + if (!WinHttpReadData(s->request, + (LPVOID)buffer, + buf_size, + &dw_bytes_read)) + { + giterr_set(GITERR_OS, "Failed to read data"); + return -1; + } + + *bytes_read = dw_bytes_read; + + return 0; +} + +static int winhttp_stream_write( + git_smart_subtransport_stream *stream, + const char *buffer, + size_t len) +{ + winhttp_stream *s = (winhttp_stream *)stream; + winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); + DWORD bytes_written; + + if (!s->request && winhttp_stream_connect(s) < 0) + return -1; + + /* Since we have to write the Content-Length header up front, we're + * basically limited to a single call to write() per request. */ + if (!s->sent_request && + !WinHttpSendRequest(s->request, + WINHTTP_NO_ADDITIONAL_HEADERS, 0, + WINHTTP_NO_REQUEST_DATA, 0, + (DWORD)len, 0)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + s->sent_request = 1; + + if (!WinHttpWriteData(s->request, + (LPCVOID)buffer, + (DWORD)len, + &bytes_written)) { + giterr_set(GITERR_OS, "Failed to send request"); + return -1; + } + + assert((DWORD)len == bytes_written); + + return 0; +} + +static void winhttp_stream_free(git_smart_subtransport_stream *stream) +{ + winhttp_stream *s = (winhttp_stream *)stream; + + if (s->request) { + WinHttpCloseHandle(s->request); + s->request = NULL; + } + + git__free(s); +} + +static int winhttp_stream_alloc(winhttp_subtransport *t, git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!stream) + return -1; + + s = (winhttp_stream *)git__calloc(sizeof(winhttp_stream), 1); + GITERR_CHECK_ALLOC(s); + + s->parent.subtransport = &t->parent; + s->parent.read = winhttp_stream_read; + s->parent.write = winhttp_stream_write; + s->parent.free = winhttp_stream_free; + + *stream = (git_smart_subtransport_stream *)s; + return 0; +} + +static int winhttp_connect( + winhttp_subtransport *t, + const char *url) +{ + wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")"; + wchar_t host[GIT_WIN_PATH]; + int32_t port; + const char *default_port; + int ret; + + if (!git__prefixcmp(url, prefix_http)) { + url = url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + t->use_ssl = 1; + } + + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) + return ret; + + t->path = strchr(url, '/'); + + /* Prepare port */ + if (git__strtol32(&port, t->port, NULL, 10) < 0) + return -1; + + /* Prepare host */ + git__utf8_to_16(host, GIT_WIN_PATH, t->host); + + /* Establish session */ + t->session = WinHttpOpen( + ua, + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0); + + if (!t->session) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + return -1; + } + + /* Establish connection */ + t->connection = WinHttpConnect( + t->session, + host, + port, + 0); + + if (!t->connection) { + giterr_set(GITERR_OS, "Failed to connect to host"); + return -1; + } + + return 0; +} + +static int winhttp_uploadpack_ls( + winhttp_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!t->connection && + winhttp_connect(t, url) < 0) + return -1; + + if (winhttp_stream_alloc(t, stream) < 0) + return -1; + + s = (winhttp_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_ls_service_url; + s->verb = get_verb; + + return 0; +} + +static int winhttp_uploadpack( + winhttp_subtransport *t, + const char *url, + git_smart_subtransport_stream **stream) +{ + winhttp_stream *s; + + if (!t->connection && + winhttp_connect(t, url) < 0) + return -1; + + if (winhttp_stream_alloc(t, stream) < 0) + return -1; + + s = (winhttp_stream *)*stream; + + s->service = upload_pack_service; + s->service_url = upload_pack_service_url; + s->verb = post_verb; + + return 0; +} + +static int winhttp_action( + git_smart_subtransport_stream **stream, + git_smart_subtransport *smart_transport, + const char *url, + git_smart_service_t action) +{ + winhttp_subtransport *t = (winhttp_subtransport *)smart_transport; + + if (!stream) + return -1; + + switch (action) + { + case GIT_SERVICE_UPLOADPACK_LS: + return winhttp_uploadpack_ls(t, url, stream); + + case GIT_SERVICE_UPLOADPACK: + return winhttp_uploadpack(t, url, stream); + } + + *stream = NULL; + return -1; +} + +static void winhttp_free(git_smart_subtransport *smart_transport) +{ + winhttp_subtransport *t = (winhttp_subtransport *) smart_transport; + + git__free(t->host); + git__free(t->port); + + if (t->cred) { + t->cred->free(t->cred); + t->cred = NULL; + } + + if (t->connection) { + WinHttpCloseHandle(t->connection); + t->connection = NULL; + } + + if (t->session) { + WinHttpCloseHandle(t->session); + t->session = NULL; + } + + git__free(t); +} + +int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner) +{ + winhttp_subtransport *t; + + if (!out) + return -1; + + t = (winhttp_subtransport *)git__calloc(sizeof(winhttp_subtransport), 1); + GITERR_CHECK_ALLOC(t); + + t->owner = (transport_smart *)owner; + t->parent.action = winhttp_action; + t->parent.free = winhttp_free; + + *out = (git_smart_subtransport *) t; + return 0; +} + +#endif /* GIT_WINHTTP */ diff --git a/src/tree.c b/src/tree.c index 8d3f2665c..6f9838880 100644 --- a/src/tree.c +++ b/src/tree.c @@ -26,7 +26,9 @@ static bool valid_filemode(const int filemode) static int valid_entry_name(const char *filename) { - return *filename != '\0' && strchr(filename, '/') == NULL; + return *filename != '\0' && strchr(filename, '/') == NULL && + strcmp(filename, "..") != 0 && strcmp(filename, ".") != 0 && + strcmp(filename, ".git") != 0; } static int entry_sort_cmp(const void *a, const void *b) @@ -353,7 +355,7 @@ static unsigned int find_next_dir(const char *dirname, git_index *index, unsigne dirlen = strlen(dirname); for (i = start; i < entries; ++i) { - git_index_entry *entry = git_index_get(index, i); + git_index_entry *entry = git_index_get_byindex(index, i); if (strlen(entry->path) < dirlen || memcmp(entry->path, dirname, dirlen) || (dirlen > 0 && entry->path[dirlen] != '/')) { @@ -372,6 +374,9 @@ static int append_entry( { git_tree_entry *entry; + if (!valid_entry_name(filename)) + return tree_error("Failed to insert entry. Invalid name for a tree entry"); + entry = alloc_entry(filename); GITERR_CHECK_ALLOC(entry); @@ -415,7 +420,7 @@ static int write_tree( * need to keep track of the current position. */ for (i = start; i < entries; ++i) { - git_index_entry *entry = git_index_get(index, i); + git_index_entry *entry = git_index_get_byindex(index, i); char *filename, *next_slash; /* @@ -491,16 +496,17 @@ on_error: return -1; } -int git_tree_create_fromindex(git_oid *oid, git_index *index) +int git_tree__write_index(git_oid *oid, git_index *index, git_repository *repo) { int ret; - git_repository *repo; - repo = (git_repository *)GIT_REFCOUNT_OWNER(index); + assert(oid && index && repo); - if (repo == NULL) - return tree_error("Failed to create tree. " - "The index file is not backed up by an existing repository"); + if (git_index_has_conflicts(index)) { + giterr_set(GITERR_INDEX, + "Cannot create a tree from a not fully merged index."); + return GIT_EUNMERGED; + } if (index->tree != NULL && index->tree->entries >= 0) { git_oid_cpy(oid, &index->tree->oid); diff --git a/src/tree.h b/src/tree.h index 24b517ce3..b67c55202 100644 --- a/src/tree.h +++ b/src/tree.h @@ -47,6 +47,12 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj); */ int git_tree__prefix_position(git_tree *tree, const char *prefix); + +/** + * Write a tree to the given repository + */ +int git_tree__write_index(git_oid *oid, git_index *index, git_repository *repo); + /** * Obsolete mode kept for compatibility reasons */ diff --git a/src/unix/posix.h b/src/unix/posix.h index bcd800301..6980c36f1 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -21,5 +21,9 @@ #define p_snprintf(b, c, f, ...) snprintf(b, c, f, __VA_ARGS__) #define p_mkstemp(p) mkstemp(p) #define p_setenv(n,v,o) setenv(n,v,o) +#define p_inet_pton(a, b, c) inet_pton(a, b, c) + +/* see win32/posix.h for explanation about why this exists */ +#define p_lstat_posixly(p,b) lstat(p,b) #endif diff --git a/src/util.c b/src/util.c index 0a82ccea6..3a08d4554 100644 --- a/src/util.c +++ b/src/util.c @@ -174,6 +174,36 @@ int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int ba return error; } +int git__strcmp(const char *a, const char *b) +{ + while (*a && *b && *a == *b) + ++a, ++b; + return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); +} + +int git__strcasecmp(const char *a, const char *b) +{ + while (*a && *b && tolower(*a) == tolower(*b)) + ++a, ++b; + return (tolower(*a) - tolower(*b)); +} + +int git__strncmp(const char *a, const char *b, size_t sz) +{ + while (sz && *a && *b && *a == *b) + --sz, ++a, ++b; + if (!sz) + return 0; + return (int)(*(const unsigned char *)a) - (int)(*(const unsigned char *)b); +} + +int git__strncasecmp(const char *a, const char *b, size_t sz) +{ + while (sz && *a && *b && tolower(*a) == tolower(*b)) + --sz, ++a, ++b; + return !sz ? 0 : (tolower(*a) - tolower(*b)); +} + void git__strntolower(char *str, size_t len) { size_t i; @@ -432,12 +462,8 @@ int git__strcmp_cb(const void *a, const void *b) int git__parse_bool(int *out, const char *value) { /* A missing value means true */ - if (value == NULL) { - *out = 1; - return 0; - } - - if (!strcasecmp(value, "true") || + if (value == NULL || + !strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on")) { *out = 1; @@ -445,7 +471,8 @@ int git__parse_bool(int *out, const char *value) } if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || - !strcasecmp(value, "off")) { + !strcasecmp(value, "off") || + value[0] == '\0') { *out = 0; return 0; } diff --git a/src/util.h b/src/util.h index 4f83d3bc1..cb1c4fdc2 100644 --- a/src/util.h +++ b/src/util.h @@ -42,12 +42,11 @@ GIT_INLINE(char *) git__strdup(const char *str) GIT_INLINE(char *) git__strndup(const char *str, size_t n) { - size_t length; + size_t length = 0; char *ptr; - length = strlen(str); - if (n < length) - length = n; + while (length < n && str[length]) + ++length; ptr = (char*)malloc(length + 1); if (!ptr) { @@ -55,7 +54,9 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n) return NULL; } - memcpy(ptr, str, length); + if (length) + memcpy(ptr, str, length); + ptr[length] = '\0'; return ptr; @@ -80,6 +81,11 @@ extern int git__prefixcmp(const char *str, const char *prefix); extern int git__prefixcmp_icase(const char *str, const char *prefix); extern int git__suffixcmp(const char *str, const char *suffix); +GIT_INLINE(int) git__signum(int val) +{ + return ((val > 0) - (val < 0)); +} + extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); @@ -128,6 +134,11 @@ extern int git__bsearch( extern int git__strcmp_cb(const void *a, const void *b); +extern int git__strcmp(const char *a, const char *b); +extern int git__strcasecmp(const char *a, const char *b); +extern int git__strncmp(const char *a, const char *b, size_t sz); +extern int git__strncasecmp(const char *a, const char *b, size_t sz); + typedef struct { short refcount; void *owner; diff --git a/src/vector.c b/src/vector.c index c6a644cc3..4763792f5 100644 --- a/src/vector.c +++ b/src/vector.c @@ -223,6 +223,20 @@ void git_vector_uniq(git_vector *v) v->length -= j - i - 1; } +void git_vector_remove_matching(git_vector *v, int (*match)(git_vector *v, size_t idx)) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < v->length; ++j) { + v->contents[i] = v->contents[j]; + + if (!match(v, i)) + i++; + } + + v->length = i; +} + void git_vector_clear(git_vector *v) { assert(v); @@ -241,3 +255,33 @@ void git_vector_swap(git_vector *a, git_vector *b) memcpy(a, b, sizeof(t)); memcpy(b, &t, sizeof(t)); } + +int git_vector_resize_to(git_vector *v, size_t new_length) +{ + if (new_length <= v->length) + return 0; + + while (new_length >= v->_alloc_size) + if (resize_vector(v) < 0) + return -1; + + memset(&v->contents[v->length], 0, + sizeof(void *) * (new_length - v->length)); + + v->length = new_length; + + return 0; +} + +int git_vector_set(void **old, git_vector *v, size_t position, void *value) +{ + if (git_vector_resize_to(v, position + 1) < 0) + return -1; + + if (old != NULL) + *old = v->contents[position]; + + v->contents[position] = value; + + return 0; +} diff --git a/src/vector.h b/src/vector.h index 49ba754f0..6d820b8fc 100644 --- a/src/vector.h +++ b/src/vector.h @@ -29,17 +29,26 @@ void git_vector_swap(git_vector *a, git_vector *b); void git_vector_sort(git_vector *v); +/** Linear search for matching entry using internal comparison function */ int git_vector_search(git_vector *v, const void *entry); + +/** Linear search for matching entry using explicit comparison function */ int git_vector_search2(git_vector *v, git_vector_cmp cmp, const void *key); +/** + * Binary search for matching entry using explicit comparison function that + * returns position where item would go if not found. + */ int git_vector_bsearch3( unsigned int *at_pos, git_vector *v, git_vector_cmp cmp, const void *key); +/** Binary search for matching entry using internal comparison function */ GIT_INLINE(int) git_vector_bsearch(git_vector *v, const void *key) { return git_vector_bsearch3(NULL, v, v->_cmp, key); } +/** Binary search for matching entry using explicit comparison function */ GIT_INLINE(int) git_vector_bsearch2( git_vector *v, git_vector_cmp cmp, const void *key) { @@ -75,5 +84,9 @@ int git_vector_insert_sorted(git_vector *v, void *element, int git_vector_remove(git_vector *v, unsigned int idx); void git_vector_pop(git_vector *v); void git_vector_uniq(git_vector *v); +void git_vector_remove_matching(git_vector *v, int (*match)(git_vector *v, size_t idx)); + +int git_vector_resize_to(git_vector *v, size_t new_length); +int git_vector_set(void **old, git_vector *v, size_t position, void *value); #endif diff --git a/src/win32/posix.h b/src/win32/posix.h index 80dcca5c1..ee61c2d05 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -48,5 +48,14 @@ extern int p_getcwd(char *buffer_out, size_t size); extern int p_rename(const char *from, const char *to); extern int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags); extern int p_send(GIT_SOCKET socket, const void *buffer, size_t length, int flags); +extern int p_inet_pton(int af, const char* src, void* dst); + +/* p_lstat is almost but not quite POSIX correct. Specifically, the use of + * ENOTDIR is wrong, in that it does not mean precisely that a non-directory + * entry was encountered. Making it correct is potentially expensive, + * however, so this is a separate version of p_lstat to use when correct + * POSIX ENOTDIR semantics is required. + */ +extern int p_lstat_posixly(const char *filename, struct stat *buf); #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 649fe9b95..0efcaf597 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -11,7 +11,7 @@ #include <errno.h> #include <io.h> #include <fcntl.h> - +#include <ws2tcpip.h> int p_unlink(const char *path) { @@ -52,17 +52,33 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) return (time_t)winTime; } -static int do_lstat(const char *file_name, struct stat *buf) +#define WIN32_IS_WSEP(CH) ((CH) == L'/' || (CH) == L'\\') + +static int do_lstat( + const char *file_name, struct stat *buf, int posix_enotdir) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t fbuf[GIT_WIN_PATH]; + wchar_t fbuf[GIT_WIN_PATH], lastch; DWORD last_error; + int flen; - git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name); + flen = git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name); + + /* truncate trailing slashes */ + for (; flen > 0; --flen) { + lastch = fbuf[flen - 1]; + if (WIN32_IS_WSEP(lastch)) + fbuf[flen - 1] = L'\0'; + else if (lastch != L'\0') + break; + } if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { int fMode = S_IREAD; + if (!buf) + return 0; + if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) fMode |= S_IFDIR; else @@ -84,48 +100,45 @@ static int do_lstat(const char *file_name, struct stat *buf) buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime)); buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime)); buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime)); + return 0; } - last_error = GetLastError(); - if (last_error == ERROR_FILE_NOT_FOUND) - errno = ENOENT; - else if (last_error == ERROR_PATH_NOT_FOUND) - errno = ENOTDIR; + errno = ENOENT; + + /* We need POSIX behavior, then ENOTDIR must set when any of the folders in the + * file path is a regular file,otherwise ENOENT must be set. + */ + if (posix_enotdir) { + /* scan up path until we find an existing item */ + while (1) { + /* remove last directory component */ + for (--flen; flen > 0 && !WIN32_IS_WSEP(fbuf[flen]); --flen); + + if (flen <= 0) + break; + + fbuf[flen] = L'\0'; + + if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { + if (!(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + errno = ENOTDIR; + break; + } + } + } return -1; } -int p_lstat(const char *file_name, struct stat *buf) +int p_lstat(const char *filename, struct stat *buf) { - int error; - size_t namelen; - char *alt_name; - - if (do_lstat(file_name, buf) == 0) - return 0; - - /* if file_name ended in a '/', Windows returned ENOENT; - * try again without trailing slashes - */ - namelen = strlen(file_name); - if (namelen && file_name[namelen-1] != '/') - return -1; - - while (namelen && file_name[namelen-1] == '/') - --namelen; - - if (!namelen) - return -1; - - alt_name = git__strndup(file_name, namelen); - if (!alt_name) - return -1; - - error = do_lstat(alt_name, buf); + return do_lstat(filename, buf, 0); +} - git__free(alt_name); - return error; +int p_lstat_posixly(const char *filename, struct stat *buf) +{ + return do_lstat(filename, buf, 1); } int p_readlink(const char *link, char *target, size_t target_len) @@ -268,7 +281,7 @@ int p_getcwd(char *buffer_out, size_t size) int p_stat(const char* path, struct stat* buf) { - return do_lstat(path, buf); + return do_lstat(path, buf, 0); } int p_chdir(const char* path) @@ -301,46 +314,42 @@ int p_hide_directory__w32(const char *path) char *p_realpath(const char *orig_path, char *buffer) { - int ret, buffer_sz = 0; + int ret; wchar_t orig_path_w[GIT_WIN_PATH]; wchar_t buffer_w[GIT_WIN_PATH]; git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path); + + /* Implicitly use GetCurrentDirectory which can be a threading issue */ ret = GetFullPathNameW(orig_path_w, GIT_WIN_PATH, buffer_w, NULL); /* According to MSDN, a return value equals to zero means a failure. */ - if (ret == 0 || ret > GIT_WIN_PATH) { + if (ret == 0 || ret > GIT_WIN_PATH) + buffer = NULL; + + else if (GetFileAttributesW(buffer_w) == INVALID_FILE_ATTRIBUTES) { buffer = NULL; - goto done; + errno = ENOENT; } - if (buffer == NULL) { - buffer_sz = WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); + else if (buffer == NULL) { + int buffer_sz = WideCharToMultiByte( + CP_UTF8, 0, buffer_w, -1, NULL, 0, NULL, NULL); if (!buffer_sz || !(buffer = (char *)git__malloc(buffer_sz)) || - !WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) + !WideCharToMultiByte( + CP_UTF8, 0, buffer_w, -1, buffer, buffer_sz, NULL, NULL)) { git__free(buffer); buffer = NULL; - goto done; - } - } else { - if (!WideCharToMultiByte(CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) { - buffer = NULL; - goto done; } } - if (!git_path_exists(buffer)) { - if (buffer_sz > 0) - git__free(buffer); - + else if (!WideCharToMultiByte( + CP_UTF8, 0, buffer_w, -1, buffer, GIT_PATH_MAX, NULL, NULL)) buffer = NULL; - errno = ENOENT; - } -done: if (buffer) git_path_mkposix(buffer); @@ -504,3 +513,45 @@ int p_gettimeofday(struct timeval *tv, struct timezone *tz) return 0; } + +int p_inet_pton(int af, const char* src, void* dst) +{ + union { + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + } sa; + size_t srcsize; + + switch(af) + { + case AF_INET: + sa.sin.sin_family = AF_INET; + srcsize = sizeof (sa.sin); + break; + case AF_INET6: + sa.sin6.sin6_family = AF_INET6; + srcsize = sizeof (sa.sin6); + break; + default: + errno = WSAEPFNOSUPPORT; + return -1; + } + + if (WSAStringToAddress(src, af, NULL, (struct sockaddr *) &sa, &srcsize) != 0) + { + errno = WSAGetLastError(); + return -1; + } + + switch(af) + { + case AF_INET: + memcpy(dst, &sa.sin.sin_addr, sizeof(sa.sin.sin_addr)); + break; + case AF_INET6: + memcpy(dst, &sa.sin6.sin6_addr, sizeof(sa.sin6.sin6_addr)); + break; + } + + return 1; +} diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 396af7cad..0659a5d1c 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -70,12 +70,12 @@ void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) } #endif -void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) +int git__utf8_to_16(wchar_t *dest, size_t length, const char *src) { - MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); + return MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); } -void git__utf16_to_8(char *out, const wchar_t *input) +int git__utf16_to_8(char *out, const wchar_t *input) { - WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL); + return WideCharToMultiByte(CP_UTF8, 0, input, -1, out, GIT_WIN_PATH, NULL, NULL); } diff --git a/src/win32/utf-conv.h b/src/win32/utf-conv.h index 3bd1549bc..f62863a76 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -12,8 +12,8 @@ #define GIT_WIN_PATH (260 + 1) -void git__utf8_to_16(wchar_t *dest, size_t length, const char *src); -void git__utf16_to_8(char *dest, const wchar_t *src); +int git__utf8_to_16(wchar_t *dest, size_t length, const char *src); +int git__utf16_to_8(char *dest, const wchar_t *src); #endif diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c index 4a317e4f3..b51d5e335 100644 --- a/tests-clar/attr/repo.c +++ b/tests-clar/attr/repo.c @@ -270,12 +270,12 @@ static void assert_proper_normalization(git_index *index, const char *filename, git_index_entry *entry; add_to_workdir(filename, CONTENT); - cl_git_pass(git_index_add(index, filename, 0)); + cl_git_pass(git_index_add_from_workdir(index, filename)); index_pos = git_index_find(index, filename); cl_assert(index_pos >= 0); - entry = git_index_get(index, index_pos); + entry = git_index_get_byindex(index, index_pos); cl_assert_equal_i(0, git_oid_streq(&entry->oid, expected_sha)); } diff --git a/tests-clar/buf/splice.c b/tests-clar/buf/splice.c new file mode 100644 index 000000000..e80c93105 --- /dev/null +++ b/tests-clar/buf/splice.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static git_buf _buf; + +void test_buf_splice__initialize(void) { + git_buf_init(&_buf, 16); +} + +void test_buf_splice__cleanup(void) { + git_buf_free(&_buf); +} + +void test_buf_splice__preprend(void) +{ + git_buf_sets(&_buf, "world!"); + + cl_git_pass(git_buf_splice(&_buf, 0, 0, "Hello Dolly", strlen("Hello "))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__append(void) +{ + git_buf_sets(&_buf, "Hello"); + + cl_git_pass(git_buf_splice(&_buf, git_buf_len(&_buf), 0, " world!", strlen(" world!"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__insert_at(void) +{ + git_buf_sets(&_buf, "Hell world!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), 0, "o", strlen("o"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__remove_at(void) +{ + git_buf_sets(&_buf, "Hello world of warcraft!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello world"), strlen(" of warcraft"), "", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace(void) +{ + git_buf_sets(&_buf, "Hell0 w0rld!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hell"), strlen("0 w0"), "o wo", strlen("o wo"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace_with_longer(void) +{ + git_buf_sets(&_buf, "Hello you!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello "), strlen("you"), "world", strlen("world"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__replace_with_shorter(void) +{ + git_buf_sets(&_buf, "Brave new world!"); + + cl_git_pass(git_buf_splice(&_buf, 0, strlen("Brave new"), "Hello", strlen("Hello"))); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__truncate(void) +{ + git_buf_sets(&_buf, "Hello world!!"); + + cl_git_pass(git_buf_splice(&_buf, strlen("Hello world!"), strlen("!"), "", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} + +void test_buf_splice__dont_do_anything(void) +{ + git_buf_sets(&_buf, "Hello world!"); + + cl_git_pass(git_buf_splice(&_buf, 3, 0, "Hello", 0)); + + cl_assert_equal_s("Hello world!", git_buf_cstr(&_buf)); +} diff --git a/tests-clar/checkout/head.c b/tests-clar/checkout/head.c index d36034c52..103b9999e 100644 --- a/tests-clar/checkout/head.c +++ b/tests-clar/checkout/head.c @@ -18,5 +18,5 @@ void test_checkout_head__checking_out_an_orphaned_head_returns_GIT_EORPHANEDHEAD { make_head_orphaned(g_repo, NON_EXISTING_HEAD); - cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL, NULL)); + cl_assert_equal_i(GIT_EORPHANEDHEAD, git_checkout_head(g_repo, NULL)); } diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index f017a0fe2..c7b19dba6 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -14,7 +14,7 @@ static void reset_index_to_treeish(git_object *treeish) cl_git_pass(git_object_peel(&tree, treeish, GIT_OBJ_TREE)); cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_index_read_tree(index, (git_tree *)tree, NULL)); + cl_git_pass(git_index_read_tree(index, (git_tree *)tree)); cl_git_pass(git_index_write(index)); git_object_free(tree); @@ -26,7 +26,7 @@ void test_checkout_index__initialize(void) git_tree *tree; memset(&g_opts, 0, sizeof(g_opts)); - g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; g_repo = cl_git_sandbox_init("testrepo"); @@ -78,8 +78,7 @@ void test_checkout_index__can_create_missing_files(void) cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); @@ -94,8 +93,8 @@ void test_checkout_index__can_remove_untracked_files(void) cl_assert_equal_i(true, git_path_isdir("./testrepo/dir/subdir/subsubdir")); - g_opts.checkout_strategy = GIT_CHECKOUT_REMOVE_UNTRACKED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + g_opts.checkout_strategy |= GIT_CHECKOUT_REMOVE_UNTRACKED; + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); } @@ -111,7 +110,7 @@ void test_checkout_index__honor_the_specified_pathspecs(void) cl_assert_equal_i(false, git_path_isfile("./testrepo/branch_file.txt")); cl_assert_equal_i(false, git_path_isfile("./testrepo/new.txt")); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); @@ -142,7 +141,7 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_mkfile("./testrepo/.gitattributes", attributes); set_core_autocrlf_to(false); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/README", "hey there\n"); test_file_contents("./testrepo/new.txt", "my new file\n"); @@ -157,7 +156,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(p_unlink("./testrepo/.gitattributes")); set_core_autocrlf_to(true); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/README", expected_readme_text); #endif @@ -172,7 +171,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) { set_repo_symlink_handling_cap_to(true); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); #ifdef GIT_WIN32 test_file_contents("./testrepo/link_to_new.txt", "new.txt"); @@ -194,7 +193,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) { set_repo_symlink_handling_cap_to(false); - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/link_to_new.txt", "new.txt"); } @@ -203,8 +202,12 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.checkout_strategy = 0; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + /* set this up to not return an error code on conflicts, but it + * still will not have permission to overwrite anything... + */ + g_opts.checkout_strategy = GIT_CHECKOUT_ALLOW_CONFLICTS; + + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); } @@ -213,8 +216,9 @@ void test_checkout_index__can_overwrite_modified_file(void) { cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); - g_opts.checkout_strategy = GIT_CHECKOUT_OVERWRITE_MODIFIED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED; + + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\n"); } @@ -224,14 +228,14 @@ void test_checkout_index__options_disable_filters(void) cl_git_mkfile("./testrepo/.gitattributes", "*.txt text eol=crlf\n"); g_opts.disable_filters = false; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\r\n"); p_unlink("./testrepo/new.txt"); g_opts.disable_filters = true; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/new.txt", "my new file\n"); } @@ -249,7 +253,7 @@ void test_checkout_index__options_dir_modes(void) reset_index_to_treeish((git_object *)commit); g_opts.dir_mode = 0701; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); cl_git_pass(p_stat("./testrepo/a", &st)); cl_assert_equal_i(st.st_mode & 0777, 0701); @@ -269,7 +273,7 @@ void test_checkout_index__options_override_file_modes(void) g_opts.file_mode = 0700; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); cl_git_pass(p_stat("./testrepo/new.txt", &st)); cl_assert_equal_i(st.st_mode & 0777, 0700); @@ -282,28 +286,30 @@ void test_checkout_index__options_open_flags(void) g_opts.file_open_flags = O_CREAT | O_RDWR | O_APPEND; - g_opts.checkout_strategy |= GIT_CHECKOUT_OVERWRITE_MODIFIED; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + g_opts.checkout_strategy |= GIT_CHECKOUT_UPDATE_MODIFIED; + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); } -struct notify_data { +struct conflict_data { const char *file; const char *sha; }; -static int notify_cb( - const char *skipped_file, +static int conflict_cb( + const char *conflict_file, const git_oid *blob_oid, - int file_mode, + unsigned int index_mode, + unsigned int wd_mode, void *payload) { - struct notify_data *expectations = (struct notify_data *)payload; + struct conflict_data *expectations = (struct conflict_data *)payload; - GIT_UNUSED(file_mode); + GIT_UNUSED(index_mode); + GIT_UNUSED(wd_mode); - cl_assert_equal_s(expectations->file, skipped_file); + cl_assert_equal_s(expectations->file, conflict_file); cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha)); return 0; @@ -311,7 +317,7 @@ static int notify_cb( void test_checkout_index__can_notify_of_skipped_files(void) { - struct notify_data data; + struct conflict_data data; cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); @@ -324,22 +330,24 @@ void test_checkout_index__can_notify_of_skipped_files(void) data.file = "new.txt"; data.sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"; - g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; - g_opts.skipped_notify_cb = notify_cb; - g_opts.notify_payload = &data; + g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS; + g_opts.conflict_cb = conflict_cb; + g_opts.conflict_payload = &data; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); } -static int dont_notify_cb( - const char *skipped_file, +static int dont_conflict_cb( + const char *conflict_file, const git_oid *blob_oid, - int file_mode, + unsigned int index_mode, + unsigned int wd_mode, void *payload) { - GIT_UNUSED(skipped_file); + GIT_UNUSED(conflict_file); GIT_UNUSED(blob_oid); - GIT_UNUSED(file_mode); + GIT_UNUSED(index_mode); + GIT_UNUSED(wd_mode); GIT_UNUSED(payload); cl_assert(false); @@ -351,12 +359,70 @@ void test_checkout_index__wont_notify_of_expected_line_ending_changes(void) { cl_git_pass(p_unlink("./testrepo/.gitattributes")); set_core_autocrlf_to(true); - + cl_git_mkfile("./testrepo/new.txt", "my new file\r\n"); - g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; - g_opts.skipped_notify_cb = dont_notify_cb; - g_opts.notify_payload = NULL; + g_opts.checkout_strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS; + g_opts.conflict_cb = dont_conflict_cb; + g_opts.conflict_payload = NULL; - cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); +} + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_index__calls_progress_callback(void) +{ + bool was_called = 0; + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); + cl_assert_equal_i(was_called, true); +} + +void test_checkout_index__can_overcome_name_clashes(void) +{ + git_index *index; + + cl_git_pass(git_repository_index(&index, g_repo)); + git_index_clear(index); + + cl_git_mkfile("./testrepo/path0", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path1", 0777)); + cl_git_mkfile("./testrepo/path1/file1", "content\r\n"); + + cl_git_pass(git_index_add_from_workdir(index, "path0")); + cl_git_pass(git_index_add_from_workdir(index, "path1/file1")); + + + cl_git_pass(p_unlink("./testrepo/path0")); + cl_git_pass(git_futils_rmdir_r( + "./testrepo/path1", NULL, GIT_RMDIR_REMOVE_FILES)); + + cl_git_mkfile("./testrepo/path1", "content\r\n"); + cl_git_pass(p_mkdir("./testrepo/path0", 0777)); + cl_git_mkfile("./testrepo/path0/file0", "content\r\n"); + + cl_assert(git_path_isfile("./testrepo/path1")); + cl_assert(git_path_isfile("./testrepo/path0/file0")); + + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS; + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); + + cl_assert(git_path_isfile("./testrepo/path1")); + cl_assert(git_path_isfile("./testrepo/path0/file0")); + + g_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + cl_git_pass(git_checkout_index(g_repo, NULL, &g_opts)); + + cl_assert(git_path_isfile("./testrepo/path0")); + cl_assert(git_path_isfile("./testrepo/path1/file1")); + + git_index_free(index); } diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 6d573bfd7..983425324 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -12,7 +12,7 @@ void test_checkout_tree__initialize(void) g_repo = cl_git_sandbox_init("testrepo"); memset(&g_opts, 0, sizeof(g_opts)); - g_opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + g_opts.checkout_strategy = GIT_CHECKOUT_SAFE; } void test_checkout_tree__cleanup(void) @@ -27,7 +27,7 @@ void test_checkout_tree__cannot_checkout_a_non_treeish(void) /* blob */ cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); - cl_git_fail(git_checkout_tree(g_repo, g_object, NULL, NULL)); + cl_git_fail(git_checkout_tree(g_repo, g_object, NULL)); } void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) @@ -41,7 +41,7 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt")); cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt")); @@ -58,8 +58,29 @@ void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); - cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt")); cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt")); } + +static void progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + *was_called = true; +} + +void test_checkout_tree__calls_progress_callback(void) +{ + bool was_called = 0; + + g_opts.progress_cb = progress; + g_opts.progress_payload = &was_called; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "master")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts)); + + cl_assert_equal_i(was_called, true); +} diff --git a/tests-clar/checkout/typechange.c b/tests-clar/checkout/typechange.c index f013617d5..cd34885de 100644 --- a/tests-clar/checkout/typechange.c +++ b/tests-clar/checkout/typechange.c @@ -40,16 +40,21 @@ void test_checkout_typechange__checkout_typechanges(void) git_object *obj; git_checkout_opts opts = {0}; - opts.checkout_strategy = - GIT_CHECKOUT_REMOVE_UNTRACKED | - GIT_CHECKOUT_CREATE_MISSING | - GIT_CHECKOUT_OVERWRITE_MODIFIED; + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* if you don't include GIT_CHECKOUT_REMOVE_UNTRACKED then on the final + * checkout which is supposed to remove all the files, we will not + * actually remove them! + */ for (i = 0; g_typechange_oids[i] != NULL; ++i) { cl_git_pass(git_revparse_single(&obj, g_repo, g_typechange_oids[i])); /* fprintf(stderr, "checking out '%s'\n", g_typechange_oids[i]); */ - cl_git_pass(git_checkout_tree(g_repo, obj, &opts, NULL)); + cl_git_pass(git_checkout_tree(g_repo, obj, &opts)); + + cl_git_pass( + git_repository_set_head_detached(g_repo, git_object_id(obj))); git_object_free(obj); diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c index 647ea5201..b718d4305 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_helpers.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "posix.h" +#include "path.h" void clar_on_init(void) { @@ -89,7 +90,11 @@ int cl_setenv(const char *name, const char *value) if (value != NULL) git__utf8_to_16(value_utf16, GIT_WIN_PATH, value); - cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL)); + /* Windows XP returns 0 (failed) when passing NULL for lpValue when lpName + * does not exist in the environment block. This behavior seems to have changed + * in later versions. Don't fail when SetEnvironmentVariable fails, if we passed + * NULL for lpValue. */ + cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL) || !value); return 0; } @@ -218,3 +223,100 @@ bool cl_is_chmod_supported(void) return _is_supported; } +const char* cl_git_fixture_url(const char *fixturename) +{ + return cl_git_path_url(cl_fixture(fixturename)); +} + +const char* cl_git_path_url(const char *path) +{ + static char url[4096]; + + const char *in_buf; + git_buf path_buf = GIT_BUF_INIT; + git_buf url_buf = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify_dir(&path_buf, path, NULL)); + cl_git_pass(git_buf_puts(&url_buf, "file://")); + +#ifdef _MSC_VER + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_buf_putc(&url_buf, '/')); +#endif + + in_buf = git_buf_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_buf_puts(&url_buf, "%20")); + else + cl_git_pass(git_buf_putc(&url_buf, *in_buf)); + + in_buf++; + } + + strncpy(url, git_buf_cstr(&url_buf), 4096); + git_buf_free(&url_buf); + git_buf_free(&path_buf); + return url; +} + +typedef struct { + const char *filename; + size_t filename_len; +} remove_data; + +static int remove_placeholders_recurs(void *_data, git_buf *path) +{ + remove_data *data = (remove_data *)_data; + size_t pathlen; + + if (git_path_isdir(path->ptr) == true) + return git_path_direach(path, remove_placeholders_recurs, data); + + pathlen = path->size; + + if (pathlen < data->filename_len) + return 0; + + /* if path ends in '/'+filename (or equals filename) */ + if (!strcmp(data->filename, path->ptr + pathlen - data->filename_len) && + (pathlen == data->filename_len || + path->ptr[pathlen - data->filename_len - 1] == '/')) + return p_unlink(path->ptr); + + return 0; +} + +int cl_git_remove_placeholders(const char *directory_path, const char *filename) +{ + int error; + remove_data data; + git_buf buffer = GIT_BUF_INIT; + + if (git_path_isdir(directory_path) == false) + return -1; + + if (git_buf_sets(&buffer, directory_path) < 0) + return -1; + + data.filename = filename; + data.filename_len = strlen(filename); + + error = remove_placeholders_recurs(&data, &buffer); + + git_buf_free(&buffer); + + return error; +} diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index ce3688ec4..91a542654 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -57,4 +57,11 @@ int cl_rename(const char *source, const char *dest); git_repository *cl_git_sandbox_init(const char *sandbox); void cl_git_sandbox_cleanup(void); +/* Local-repo url helpers */ +const char* cl_git_fixture_url(const char *fixturename); +const char* cl_git_path_url(const char *path); + +/* Test repository cleaner */ +int cl_git_remove_placeholders(const char *directory_path, const char *filename); + #endif diff --git a/tests-clar/clone/network.c b/tests-clar/clone/network.c index 1ebdfb5d1..1304f7728 100644 --- a/tests-clar/clone/network.c +++ b/tests-clar/clone/network.c @@ -43,7 +43,7 @@ void test_clone_network__network_bare(void) cl_set_cleanup(&cleanup_repository, "./test"); - cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL)); + cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "./test", NULL, NULL)); cl_assert(git_repository_is_bare(g_repo)); cl_git_pass(git_remote_load(&origin, g_repo, "origin")); @@ -91,18 +91,36 @@ void test_clone_network__can_prevent_the_checkout_of_a_standard_repo(void) git_buf_free(&path); } +static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot); + (*was_called) = true; +} + +static void fetch_progress(const git_transfer_progress *stats, void *payload) +{ + bool *was_called = (bool*)payload; + GIT_UNUSED(stats); + (*was_called) = true; +} + void test_clone_network__can_checkout_a_cloned_repo(void) { - git_checkout_opts opts; + git_checkout_opts opts = {0}; git_buf path = GIT_BUF_INIT; git_reference *head; + bool checkout_progress_cb_was_called = false, + fetch_progress_cb_was_called = false; - memset(&opts, 0, sizeof(opts)); - opts.checkout_strategy = GIT_CHECKOUT_CREATE_MISSING; + opts.checkout_strategy = GIT_CHECKOUT_SAFE; + opts.progress_cb = &checkout_progress; + opts.progress_payload = &checkout_progress_cb_was_called; cl_set_cleanup(&cleanup_repository, "./default-checkout"); - cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout", NULL, NULL, &opts)); + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./default-checkout", + &fetch_progress, &fetch_progress_cb_was_called, &opts)); cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt")); cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path))); @@ -111,6 +129,9 @@ void test_clone_network__can_checkout_a_cloned_repo(void) cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); cl_assert_equal_s("refs/heads/master", git_reference_target(head)); + cl_assert_equal_i(true, checkout_progress_cb_was_called); + cl_assert_equal_i(true, fetch_progress_cb_was_called); + git_reference_free(head); git_buf_free(&path); } diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c index 81f95b9b3..59f43362f 100644 --- a/tests-clar/clone/nonetwork.c +++ b/tests-clar/clone/nonetwork.c @@ -3,7 +3,6 @@ #include "git2/clone.h" #include "repository.h" -#define DO_LOCAL_TEST 0 #define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" static git_repository *g_repo; @@ -20,81 +19,29 @@ static void cleanup_repository(void *path) cl_fixture_cleanup((const char *)path); } -// TODO: This is copy/pasted from network/remotelocal.c. -static void build_local_file_url(git_buf *out, const char *fixture) -{ - const char *in_buf; - - git_buf path_buf = GIT_BUF_INIT; - - cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL)); - cl_git_pass(git_buf_puts(out, "file://")); - -#ifdef GIT_WIN32 - /* - * A FILE uri matches the following format: file://[host]/path - * where "host" can be empty and "path" is an absolute path to the resource. - * - * In this test, no hostname is used, but we have to ensure the leading triple slashes: - * - * *nix: file:///usr/home/... - * Windows: file:///C:/Users/... - */ - cl_git_pass(git_buf_putc(out, '/')); -#endif - - in_buf = git_buf_cstr(&path_buf); - - /* - * A very hacky Url encoding that only takes care of escaping the spaces - */ - while (*in_buf) { - if (*in_buf == ' ') - cl_git_pass(git_buf_puts(out, "%20")); - else - cl_git_pass(git_buf_putc(out, *in_buf)); - - in_buf++; - } - - git_buf_free(&path_buf); -} - void test_clone_nonetwork__bad_url(void) { /* Clone should clean up the mess if the URL isn't a git repository */ cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo", NULL, NULL, NULL)); cl_assert(!git_path_exists("./foo")); - cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL)); + cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git", NULL, NULL)); cl_assert(!git_path_exists("./foo.git")); } void test_clone_nonetwork__local(void) { - git_buf src = GIT_BUF_INIT; - build_local_file_url(&src, cl_fixture("testrepo.git")); - -#if DO_LOCAL_TEST + const char *src = cl_git_fixture_url("testrepo.git"); cl_set_cleanup(&cleanup_repository, "./local"); - cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL)); -#endif - - git_buf_free(&src); + cl_git_pass(git_clone(&g_repo, src, "./local", NULL, NULL, NULL)); } void test_clone_nonetwork__local_bare(void) { - git_buf src = GIT_BUF_INIT; - build_local_file_url(&src, cl_fixture("testrepo.git")); - -#if DO_LOCAL_TEST + const char *src = cl_git_fixture_url("testrepo.git"); cl_set_cleanup(&cleanup_repository, "./local.git"); - cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); -#endif - - git_buf_free(&src); + cl_git_pass(git_clone_bare(&g_repo, src, "./local.git", NULL, NULL)); } void test_clone_nonetwork__fail_when_the_target_is_a_file(void) diff --git a/tests-clar/config/config_helpers.c b/tests-clar/config/config_helpers.c new file mode 100644 index 000000000..53bd945a0 --- /dev/null +++ b/tests-clar/config/config_helpers.c @@ -0,0 +1,37 @@ +#include "clar_libgit2.h" +#include "config_helpers.h" +#include "repository.h" + +void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist) +{ + git_config *config; + const char *out; + int result; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + result = git_config_get_string(&out, config, name); + + if (is_supposed_to_exist) + cl_git_pass(result); + else + cl_assert_equal_i(GIT_ENOTFOUND, result); +} + +void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value) +{ + git_config *config; + const char *out; + + cl_git_pass(git_repository_config__weakptr(&config, repo)); + + cl_git_pass(git_config_get_string(&out, config, name)); + + cl_assert_equal_s(expected_value, out); +} diff --git a/tests-clar/config/config_helpers.h b/tests-clar/config/config_helpers.h new file mode 100644 index 000000000..b887b3d38 --- /dev/null +++ b/tests-clar/config/config_helpers.h @@ -0,0 +1,9 @@ +extern void assert_config_entry_existence( + git_repository *repo, + const char *name, + bool is_supposed_to_exist); + +extern void assert_config_entry_value( + git_repository *repo, + const char *name, + const char *expected_value); diff --git a/tests-clar/config/configlevel.c b/tests-clar/config/configlevel.c index d947856fa..1c22e8d9f 100644 --- a/tests-clar/config/configlevel.c +++ b/tests-clar/config/configlevel.c @@ -57,3 +57,15 @@ void test_config_configlevel__can_read_from_a_single_level_focused_file_after_pa git_config_free(single_level_cfg); } + +void test_config_configlevel__fetching_a_level_from_an_empty_compound_config_returns_ENOTFOUND(void) +{ + git_config *cfg; + git_config *local_cfg; + + cl_git_pass(git_config_new(&cfg)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_level(&local_cfg, cfg, GIT_CONFIG_LEVEL_LOCAL)); + + git_config_free(cfg); +} diff --git a/tests-clar/config/new.c b/tests-clar/config/new.c index 6bd719fba..dd6dbca9e 100644 --- a/tests-clar/config/new.c +++ b/tests-clar/config/new.c @@ -11,6 +11,7 @@ void test_config_new__write_new_config(void) const char *out; git_config *config; + cl_git_mkfile(TEST_CONFIG, ""); cl_git_pass(git_config_open_ondisk(&config, TEST_CONFIG)); cl_git_pass(git_config_set_string(config, "color.ui", "auto")); diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index cf781e6bf..e0171a593 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -87,12 +87,20 @@ void test_config_read__lone_variable(void) cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config4"))); + cl_git_fail(git_config_get_int32(&i, cfg, "some.section.variable")); + cl_git_pass(git_config_get_string(&str, cfg, "some.section.variable")); - cl_assert(str == NULL); + cl_assert_equal_s(str, ""); cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variable")); cl_assert(i == 1); + cl_git_pass(git_config_get_string(&str, cfg, "some.section.variableeq")); + cl_assert_equal_s(str, ""); + + cl_git_pass(git_config_get_bool(&i, cfg, "some.section.variableeq")); + cl_assert(i == 0); + git_config_free(cfg); } @@ -305,7 +313,7 @@ void test_config_read__read_git_config_entry(void) cl_git_pass(git_config_add_file_ondisk(cfg, cl_fixture("config/config9"), GIT_CONFIG_LEVEL_SYSTEM, 0)); - cl_git_pass(git_config_get_config_entry(&entry, cfg, "core.dummy2")); + cl_git_pass(git_config_get_entry(&entry, cfg, "core.dummy2")); cl_assert_equal_s("core.dummy2", entry->name); cl_assert_equal_s("42", entry->value); cl_assert_equal_i(GIT_CONFIG_LEVEL_SYSTEM, entry->level); @@ -422,3 +430,22 @@ void test_config_read__simple_read_from_specific_level(void) git_config_free(cfg_specific); git_config_free(cfg); } + +void test_config_read__can_load_and_parse_an_empty_config_file(void) +{ + git_config *cfg; + int i; + + cl_git_mkfile("./empty", ""); + cl_git_pass(git_config_open_ondisk(&cfg, "./empty")); + cl_assert_equal_i(GIT_ENOTFOUND, git_config_get_int32(&i, cfg, "nope.neither")); + + git_config_free(cfg); +} + +void test_config_read__cannot_load_a_non_existing_config_file(void) +{ + git_config *cfg; + + cl_assert_equal_i(GIT_ENOTFOUND, git_config_open_ondisk(&cfg, "./no.config")); +} diff --git a/tests-clar/config/refresh.c b/tests-clar/config/refresh.c new file mode 100644 index 000000000..99d677f0e --- /dev/null +++ b/tests-clar/config/refresh.c @@ -0,0 +1,67 @@ +#include "clar_libgit2.h" + +#define TEST_FILE "config.refresh" + +void test_config_refresh__initialize(void) +{ +} + +void test_config_refresh__cleanup(void) +{ + cl_fixture_cleanup(TEST_FILE); +} + +void test_config_refresh__update_value(void) +{ + git_config *cfg; + int32_t v; + + cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n"); + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + + cl_git_rewritefile(TEST_FILE, "[section]\n\tvalue = 10\n\n"); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + + cl_git_pass(git_config_refresh(cfg)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(10, v); + + git_config_free(cfg); +} + +void test_config_refresh__delete_value(void) +{ + git_config *cfg; + int32_t v; + + cl_git_mkfile(TEST_FILE, "[section]\n\tvalue = 1\n\n"); + + /* By freeing the config, we make sure we flush the values */ + cl_git_pass(git_config_open_ondisk(&cfg, TEST_FILE)); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + cl_git_fail(git_config_get_int32(&v, cfg, "section.newval")); + + cl_git_rewritefile(TEST_FILE, "[section]\n\tnewval = 10\n\n"); + + cl_git_pass(git_config_get_int32(&v, cfg, "section.value")); + cl_assert_equal_i(1, v); + cl_git_fail(git_config_get_int32(&v, cfg, "section.newval")); + + cl_git_pass(git_config_refresh(cfg)); + + cl_git_fail(git_config_get_int32(&v, cfg, "section.value")); + cl_git_pass(git_config_get_int32(&v, cfg, "section.newval")); + cl_assert_equal_i(10, v); + + git_config_free(cfg); +} diff --git a/tests-clar/core/copy.c b/tests-clar/core/copy.c index d0b21f6ec..c0c59c056 100644 --- a/tests-clar/core/copy.c +++ b/tests-clar/core/copy.c @@ -41,7 +41,7 @@ void test_core_copy__file_in_dir(void) cl_assert(S_ISREG(st.st_mode)); cl_assert(strlen(content) == (size_t)st.st_size); - cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r("an_dir", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("an_dir")); } @@ -95,7 +95,7 @@ void test_core_copy__tree(void) cl_assert(S_ISLNK(st.st_mode)); #endif - cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r("t1", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("t1")); /* copy with empty dirs, no links, yes dotfiles, no overwrite */ @@ -119,8 +119,8 @@ void test_core_copy__tree(void) cl_git_fail(git_path_lstat("t2/c/d/l1", &st)); #endif - cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("t2")); - cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); } diff --git a/tests-clar/core/env.c b/tests-clar/core/env.c index 288222d29..d849f76ed 100644 --- a/tests-clar/core/env.c +++ b/tests-clar/core/env.c @@ -84,13 +84,15 @@ void test_core_env__0(void) cl_git_mkfile(path.ptr, "find me"); git_buf_rtruncate_at_char(&path, '/'); - cl_git_fail(git_futils_find_global_file(&found, testfile)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile)); setenv_and_check("HOME", path.ptr); cl_git_pass(git_futils_find_global_file(&found, testfile)); cl_setenv("HOME", env_save[0]); - cl_git_fail(git_futils_find_global_file(&found, testfile)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile)); #ifdef GIT_WIN32 setenv_and_check("HOMEDRIVE", NULL); @@ -106,7 +108,8 @@ void test_core_env__0(void) if (root >= 0) { setenv_and_check("USERPROFILE", NULL); - cl_git_fail(git_futils_find_global_file(&found, testfile)); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&found, testfile)); old = path.ptr[root]; path.ptr[root] = '\0'; @@ -128,7 +131,8 @@ void test_core_env__1(void) { git_buf path = GIT_BUF_INIT; - cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile")); cl_git_pass(cl_setenv("HOME", "doesnotexist")); #ifdef GIT_WIN32 @@ -136,7 +140,8 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); #endif - cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile")); cl_git_pass(cl_setenv("HOME", NULL)); #ifdef GIT_WIN32 @@ -144,13 +149,16 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("USERPROFILE", NULL)); #endif - cl_must_fail(git_futils_find_global_file(&path, "nonexistentfile")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_global_file(&path, "nonexistentfile")); - cl_must_fail(git_futils_find_system_file(&path, "nonexistentfile")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_system_file(&path, "nonexistentfile")); #ifdef GIT_WIN32 cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); - cl_must_fail(git_futils_find_system_file(&path, "nonexistentfile")); + cl_assert_equal_i( + GIT_ENOTFOUND, git_futils_find_system_file(&path, "nonexistentfile")); #endif git_buf_free(&path); diff --git a/tests-clar/core/mkdir.c b/tests-clar/core/mkdir.c index e5dc6654b..1e50b4336 100644 --- a/tests-clar/core/mkdir.c +++ b/tests-clar/core/mkdir.c @@ -6,11 +6,11 @@ static void cleanup_basic_dirs(void *ref) { GIT_UNUSED(ref); - git_futils_rmdir_r("d0", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); - git_futils_rmdir_r("d1", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); - git_futils_rmdir_r("d2", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); - git_futils_rmdir_r("d3", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); - git_futils_rmdir_r("d4", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("d0", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d1", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d2", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d3", NULL, GIT_RMDIR_EMPTY_HIERARCHY); + git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } void test_core_mkdir__basic(void) @@ -56,7 +56,7 @@ void test_core_mkdir__basic(void) static void cleanup_basedir(void *ref) { GIT_UNUSED(ref); - git_futils_rmdir_r("base", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("base", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } void test_core_mkdir__with_base(void) @@ -108,7 +108,7 @@ static void cleanup_chmod_root(void *ref) git__free(mode); } - git_futils_rmdir_r("r", NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("r", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } static void check_mode(mode_t expected, mode_t actual) diff --git a/tests-clar/core/rmdir.c b/tests-clar/core/rmdir.c index 9ada8f426..f0b0bfa42 100644 --- a/tests-clar/core/rmdir.c +++ b/tests-clar/core/rmdir.c @@ -30,7 +30,7 @@ void test_core_rmdir__initialize(void) /* make sure empty dir can be deleted recusively */ void test_core_rmdir__delete_recursive(void) { - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); } /* make sure non-empty dir cannot be deleted recusively */ @@ -42,15 +42,15 @@ void test_core_rmdir__fail_to_delete_non_empty_dir(void) cl_git_mkfile(git_buf_cstr(&file), "dummy"); - cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); + cl_git_fail(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); cl_must_pass(p_unlink(file.ptr)); - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_EMPTY_HIERARCHY)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); git_buf_free(&file); } -void test_core_rmdir__can_skip__non_empty_dir(void) +void test_core_rmdir__can_skip_non_empty_dir(void) { git_buf file = GIT_BUF_INIT; @@ -58,11 +58,41 @@ void test_core_rmdir__can_skip__non_empty_dir(void) cl_git_mkfile(git_buf_cstr(&file), "dummy"); - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_ONLY_EMPTY_DIRS)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_SKIP_NONEMPTY)); cl_assert(git_path_exists(git_buf_cstr(&file)) == true); - cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(git_path_exists(empty_tmp_dir) == false); git_buf_free(&file); } + +void test_core_rmdir__can_remove_empty_parents(void) +{ + git_buf file = GIT_BUF_INIT; + + cl_git_pass( + git_buf_joinpath(&file, empty_tmp_dir, "/one/two_two/three/file.txt")); + cl_git_mkfile(git_buf_cstr(&file), "dummy"); + cl_assert(git_path_isfile(git_buf_cstr(&file))); + + cl_git_pass(git_futils_rmdir_r("one/two_two/three/file.txt", empty_tmp_dir, + GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_EMPTY_PARENTS)); + + cl_assert(!git_path_exists(git_buf_cstr(&file))); + + git_buf_rtruncate_at_char(&file, '/'); /* three (only contained file.txt) */ + cl_assert(!git_path_exists(git_buf_cstr(&file))); + + git_buf_rtruncate_at_char(&file, '/'); /* two_two (only contained three) */ + cl_assert(!git_path_exists(git_buf_cstr(&file))); + + git_buf_rtruncate_at_char(&file, '/'); /* one (contained two_one also) */ + cl_assert(git_path_exists(git_buf_cstr(&file))); + + cl_assert(git_path_exists(empty_tmp_dir) == true); + + git_buf_free(&file); + + cl_git_pass(git_futils_rmdir_r(empty_tmp_dir, NULL, GIT_RMDIR_EMPTY_HIERARCHY)); +} diff --git a/tests-clar/core/stat.c b/tests-clar/core/stat.c new file mode 100644 index 000000000..2e4abfb79 --- /dev/null +++ b/tests-clar/core/stat.c @@ -0,0 +1,97 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "path.h" +#include "posix.h" + +void test_core_stat__initialize(void) +{ + cl_git_pass(git_futils_mkdir("root/d1/d2", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_mkfile("root/file", "whatever\n"); + cl_git_mkfile("root/d1/file", "whatever\n"); +} + +void test_core_stat__cleanup(void) +{ + git_futils_rmdir_r("root", NULL, GIT_RMDIR_REMOVE_FILES); +} + +#define cl_assert_error(val) \ + do { err = errno; cl_assert_equal_i((val), err); } while (0) + +void test_core_stat__0(void) +{ + struct stat st; + int err; + + cl_assert_equal_i(0, p_lstat("root", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/", &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert_error(0); + + cl_assert_equal_i(0, p_lstat("root/d1/file", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert_error(0); + + cl_assert(p_lstat("root/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/missing/but/could/be/created", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/missing/deeper/path", &st) < 0); + cl_assert_error(ENOENT); + + cl_assert(p_lstat_posixly("root/d1/file/deeper/path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat("root/file/invalid/deeper_path", &st) < 0); +#ifdef GIT_WIN32 + cl_assert_error(ENOENT); +#else + cl_assert_error(ENOTDIR); +#endif + + cl_assert(p_lstat_posixly("root/file/invalid/deeper_path", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/extra", &st) < 0); + cl_assert_error(ENOTDIR); + + cl_assert(p_lstat_posixly("root/d1/file/further/invalid/items", &st) < 0); + cl_assert_error(ENOTDIR); +} + diff --git a/tests-clar/core/vector.c b/tests-clar/core/vector.c index ef3d6c36d..b165905ae 100644 --- a/tests-clar/core/vector.c +++ b/tests-clar/core/vector.c @@ -189,3 +189,87 @@ void test_core_vector__5(void) git_vector_free(&x); } + +static int remove_ones(git_vector *v, size_t idx) +{ + return (git_vector_get(v, idx) == (void *)0x001); +} + +/* Test removal based on callback */ +void test_core_vector__remove_matching(void) +{ + git_vector x; + size_t i; + void *compare; + + git_vector_init(&x, 1, NULL); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 1); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 3); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 0); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x001); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 2); + + git_vector_foreach(&x, i, compare) { + cl_assert(compare != (void *)0x001); + } + + git_vector_clear(&x); + + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + git_vector_insert(&x, (void*) 0x002); + git_vector_insert(&x, (void*) 0x003); + + cl_assert(x.length == 4); + git_vector_remove_matching(&x, remove_ones); + cl_assert(x.length == 4); + + git_vector_free(&x); +} diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index d5cf41e99..0a37e829d 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -59,8 +59,8 @@ void test_diff_blob__can_compare_text_blobs(void) a, b, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_mods); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(1, expected.hunks); cl_assert_equal_i(6, expected.lines); @@ -74,8 +74,8 @@ void test_diff_blob__can_compare_text_blobs(void) b, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_mods); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(1, expected.hunks); cl_assert_equal_i(15, expected.lines); @@ -89,8 +89,8 @@ void test_diff_blob__can_compare_text_blobs(void) a, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_mods); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(1, expected.hunks); cl_assert_equal_i(13, expected.lines); @@ -103,8 +103,8 @@ void test_diff_blob__can_compare_text_blobs(void) c, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_mods); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(2, expected.hunks); cl_assert_equal_i(14, expected.lines); @@ -125,8 +125,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_dels); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(1, expected.hunks); cl_assert_equal_i(14, expected.hunk_old_lines); @@ -140,8 +140,8 @@ void test_diff_blob__can_compare_against_null_blobs(void) d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_adds); - cl_assert(expected.at_least_one_of_them_is_binary == false); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expected.files_binary); cl_assert_equal_i(1, expected.hunks); cl_assert_equal_i(14, expected.hunk_new_lines); @@ -154,10 +154,9 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( alien, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_dels); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_DELETED]); cl_assert_equal_i(0, expected.hunks); cl_assert_equal_i(0, expected.lines); @@ -166,20 +165,19 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( NULL, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_adds); + cl_assert_equal_i(1, expected.files_binary); + cl_assert_equal_i(1, expected.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(0, expected.hunks); cl_assert_equal_i(0, expected.lines); } -static void assert_identical_blobs_comparison(diff_expects expected) +static void assert_identical_blobs_comparison(diff_expects *expected) { - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_unmodified); - cl_assert_equal_i(0, expected.hunks); - cl_assert_equal_i(0, expected.lines); + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(0, expected->hunks); + cl_assert_equal_i(0, expected->lines); } void test_diff_blob__can_compare_identical_blobs(void) @@ -187,32 +185,32 @@ void test_diff_blob__can_compare_identical_blobs(void) cl_git_pass(git_diff_blobs( d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.at_least_one_of_them_is_binary == false); - assert_identical_blobs_comparison(expected); + cl_assert_equal_i(0, expected.files_binary); + assert_identical_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( NULL, NULL, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.at_least_one_of_them_is_binary == false); - assert_identical_blobs_comparison(expected); + cl_assert_equal_i(0, expected.files_binary); + assert_identical_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( alien, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.at_least_one_of_them_is_binary == true); - assert_identical_blobs_comparison(expected); + cl_assert(expected.files_binary > 0); + assert_identical_blobs_comparison(&expected); } -static void assert_binary_blobs_comparison(diff_expects expected) +static void assert_binary_blobs_comparison(diff_expects *expected) { - cl_assert(expected.at_least_one_of_them_is_binary == true); + cl_assert(expected->files_binary > 0); - cl_assert_equal_i(1, expected.files); - cl_assert_equal_i(1, expected.file_mods); - cl_assert_equal_i(0, expected.hunks); - cl_assert_equal_i(0, expected.lines); + cl_assert_equal_i(1, expected->files); + cl_assert_equal_i(1, expected->file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expected->hunks); + cl_assert_equal_i(0, expected->lines); } void test_diff_blob__can_compare_two_binary_blobs(void) @@ -227,14 +225,14 @@ void test_diff_blob__can_compare_two_binary_blobs(void) cl_git_pass(git_diff_blobs( alien, heart, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - assert_binary_blobs_comparison(expected); + assert_binary_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( heart, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - assert_binary_blobs_comparison(expected); + assert_binary_blobs_comparison(&expected); git_blob_free(heart); } @@ -244,14 +242,14 @@ void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) cl_git_pass(git_diff_blobs( alien, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - assert_binary_blobs_comparison(expected); + assert_binary_blobs_comparison(&expected); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( d, alien, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - assert_binary_blobs_comparison(expected); + assert_binary_blobs_comparison(&expected); } /* diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index de0e7e074..992c87d4c 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -32,20 +32,13 @@ int diff_file_fn( e->files++; - if (delta->binary) { - e->at_least_one_of_them_is_binary = true; + if (delta->binary) e->files_binary++; - } - switch (delta->status) { - case GIT_DELTA_ADDED: e->file_adds++; break; - case GIT_DELTA_DELETED: e->file_dels++; break; - case GIT_DELTA_MODIFIED: e->file_mods++; break; - case GIT_DELTA_IGNORED: e->file_ignored++; break; - case GIT_DELTA_UNTRACKED: e->file_untracked++; break; - case GIT_DELTA_UNMODIFIED: e->file_unmodified++; break; - default: break; - } + cl_assert(delta->status <= GIT_DELTA_TYPECHANGE); + + e->file_status[delta->status] += 1; + return 0; } diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h index 629130934..6ff493d49 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests-clar/diff/diff_helpers.h @@ -8,12 +8,7 @@ typedef struct { int files; int files_binary; - int file_adds; - int file_dels; - int file_mods; - int file_ignored; - int file_untracked; - int file_unmodified; + int file_status[10]; /* indexed by git_delta_t value */ int hunks; int hunk_new_lines; @@ -23,8 +18,6 @@ typedef struct { int line_ctxt; int line_adds; int line_dels; - - bool at_least_one_of_them_is_binary; } diff_expects; extern int diff_file_fn( diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c index f6d9bfc38..133392c21 100644 --- a/tests-clar/diff/diffiter.c +++ b/tests-clar/diff/diffiter.c @@ -16,7 +16,7 @@ void test_diff_diffiter__create(void) git_diff_list *diff; size_t d, num_d; - cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { @@ -34,7 +34,7 @@ void test_diff_diffiter__iterate_files(void) size_t d, num_d; int count = 0; - cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL)); num_d = git_diff_num_deltas(diff); cl_assert_equal_i(6, (int)num_d); @@ -57,7 +57,7 @@ void test_diff_diffiter__iterate_files_2(void) size_t d, num_d; int count = 0; - cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL)); num_d = git_diff_num_deltas(diff); cl_assert_equal_i(8, (int)num_d); @@ -85,7 +85,7 @@ void test_diff_diffiter__iterate_files_and_hunks(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts)); num_d = git_diff_num_deltas(diff); @@ -138,7 +138,7 @@ void test_diff_diffiter__max_size_threshold(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { @@ -173,7 +173,7 @@ void test_diff_diffiter__max_size_threshold(void) opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; opts.max_size = 50; /* treat anything over 50 bytes as binary! */ - cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { @@ -216,7 +216,7 @@ void test_diff_diffiter__iterate_all(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { @@ -292,7 +292,7 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_workdir_to_index(repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, &opts)); num_d = git_diff_num_deltas(diff); @@ -342,3 +342,102 @@ void test_diff_diffiter__iterate_randomly_while_saving_state(void) cl_assert_equal_i(8, exp.hunks); cl_assert_equal_i(14, exp.lines); } + +/* This output is taken directly from `git diff` on the status test data */ +static const char *expected_patch_text[8] = { + /* 0 */ + "diff --git a/file_deleted b/file_deleted\n" + "deleted file mode 100644\n" + "index 5452d32..0000000\n" + "--- a/file_deleted\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-file_deleted\n", + /* 1 */ + "diff --git a/modified_file b/modified_file\n" + "index 452e424..0a53963 100644\n" + "--- a/modified_file\n" + "+++ b/modified_file\n" + "@@ -1 +1,2 @@\n" + " modified_file\n" + "+modified_file\n", + /* 2 */ + "diff --git a/staged_changes_file_deleted b/staged_changes_file_deleted\n" + "deleted file mode 100644\n" + "index a6be623..0000000\n" + "--- a/staged_changes_file_deleted\n" + "+++ /dev/null\n" + "@@ -1,2 +0,0 @@\n" + "-staged_changes_file_deleted\n" + "-staged_changes_file_deleted\n", + /* 3 */ + "diff --git a/staged_changes_modified_file b/staged_changes_modified_file\n" + "index 906ee77..011c344 100644\n" + "--- a/staged_changes_modified_file\n" + "+++ b/staged_changes_modified_file\n" + "@@ -1,2 +1,3 @@\n" + " staged_changes_modified_file\n" + " staged_changes_modified_file\n" + "+staged_changes_modified_file\n", + /* 4 */ + "diff --git a/staged_new_file_deleted_file b/staged_new_file_deleted_file\n" + "deleted file mode 100644\n" + "index 90b8c29..0000000\n" + "--- a/staged_new_file_deleted_file\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-staged_new_file_deleted_file\n", + /* 5 */ + "diff --git a/staged_new_file_modified_file b/staged_new_file_modified_file\n" + "index ed06290..8b090c0 100644\n" + "--- a/staged_new_file_modified_file\n" + "+++ b/staged_new_file_modified_file\n" + "@@ -1 +1,2 @@\n" + " staged_new_file_modified_file\n" + "+staged_new_file_modified_file\n", + /* 6 */ + "diff --git a/subdir/deleted_file b/subdir/deleted_file\n" + "deleted file mode 100644\n" + "index 1888c80..0000000\n" + "--- a/subdir/deleted_file\n" + "+++ /dev/null\n" + "@@ -1 +0,0 @@\n" + "-subdir/deleted_file\n", + /* 7 */ + "diff --git a/subdir/modified_file b/subdir/modified_file\n" + "index a619198..57274b7 100644\n" + "--- a/subdir/modified_file\n" + "+++ b/subdir/modified_file\n" + "@@ -1 +1,2 @@\n" + " subdir/modified_file\n" + "+subdir/modified_file\n" +}; + +void test_diff_diffiter__iterate_and_generate_patch_text(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_list *diff; + size_t d, num_d; + + cl_git_pass(git_diff_workdir_to_index(&diff, repo, NULL, NULL)); + + num_d = git_diff_num_deltas(diff); + cl_assert_equal_i(8, (int)num_d); + + for (d = 0; d < num_d; ++d) { + git_diff_patch *patch; + char *text; + + cl_git_pass(git_diff_get_patch(&patch, NULL, diff, d)); + cl_assert(patch != NULL); + + cl_git_pass(git_diff_patch_to_str(&text, patch)); + + cl_assert_equal_s(expected_patch_text[d], text); + + git__free(text); + git_diff_patch_free(patch); + } + + git_diff_list_free(diff); +} diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c index 7c4bddb90..4b96bfa09 100644 --- a/tests-clar/diff/index.c +++ b/tests-clar/diff/index.c @@ -32,7 +32,7 @@ void test_diff_index__0(void) memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); @@ -45,9 +45,9 @@ void test_diff_index__0(void) * - mv .git .gitted */ cl_assert_equal_i(8, exp.files); - cl_assert_equal_i(3, exp.file_adds); - cl_assert_equal_i(2, exp.file_dels); - cl_assert_equal_i(3, exp.file_mods); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(8, exp.hunks); @@ -60,7 +60,7 @@ void test_diff_index__0(void) diff = NULL; memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); + cl_git_pass(git_diff_index_to_tree(&diff, g_repo, b, NULL, &opts)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); @@ -73,9 +73,9 @@ void test_diff_index__0(void) * - mv .git .gitted */ cl_assert_equal_i(12, exp.files); - cl_assert_equal_i(7, exp.file_adds); - cl_assert_equal_i(2, exp.file_dels); - cl_assert_equal_i(3, exp.file_mods); + cl_assert_equal_i(7, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(12, exp.hunks); @@ -125,7 +125,7 @@ void test_diff_index__1(void) memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts)); cl_assert_equal_i( GIT_EUSER, diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index c2ab9940b..368903200 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -350,7 +350,7 @@ static void index_iterator_test( int count = 0; git_repository *repo = cl_git_sandbox_init(sandbox); - cl_git_pass(git_iterator_for_index_range(&i, repo, start, end)); + cl_git_pass(git_iterator_for_repo_index_range(&i, repo, start, end)); cl_git_pass(git_iterator_current(i, &entry)); while (entry != NULL) { diff --git a/tests-clar/diff/patch.c b/tests-clar/diff/patch.c index e8468386c..6aaf7651f 100644 --- a/tests-clar/diff/patch.c +++ b/tests-clar/diff/patch.c @@ -88,7 +88,7 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void) one = resolve_commit_oid_to_tree(g_repo, one_sha); another = resolve_commit_oid_to_tree(g_repo, another_sha); - cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, one, another, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); cl_git_pass(git_diff_print_patch(diff, NULL, check_removal_cb)); @@ -97,3 +97,33 @@ void test_diff_patch__can_properly_display_the_removal_of_a_file(void) git_tree_free(another); git_tree_free(one); } + +void test_diff_patch__to_string(void) +{ + const char *one_sha = "26a125e"; + const char *another_sha = "735b6a2"; + git_tree *one, *another; + git_diff_list *diff; + git_diff_patch *patch; + char *text; + const char *expected = "diff --git a/subdir.txt b/subdir.txt\ndeleted file mode 100644\nindex e8ee89e..0000000\n--- a/subdir.txt\n+++ /dev/null\n@@ -1,2 +0,0 @@\n-Is it a bird?\n-Is it a plane?\n"; + + one = resolve_commit_oid_to_tree(g_repo, one_sha); + another = resolve_commit_oid_to_tree(g_repo, another_sha); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, one, another, NULL)); + + cl_assert_equal_i(1, git_diff_num_deltas(diff)); + + cl_git_pass(git_diff_get_patch(&patch, NULL, diff, 0)); + + cl_git_pass(git_diff_patch_to_str(&text, patch)); + + cl_assert_equal_s(expected, text); + + git__free(text); + git_diff_patch_free(patch); + git_diff_list_free(diff); + git_tree_free(another); + git_tree_free(one); +} diff --git a/tests-clar/diff/rename.c b/tests-clar/diff/rename.c new file mode 100644 index 000000000..1ea2e3fc9 --- /dev/null +++ b/tests-clar/diff/rename.c @@ -0,0 +1,105 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +static git_repository *g_repo = NULL; + +void test_diff_rename__initialize(void) +{ + g_repo = cl_git_sandbox_init("renames"); +} + +void test_diff_rename__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +/* + * Renames repo has: + * + * commit 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 - + * serving.txt (25 lines) + * sevencities.txt (50 lines) + * commit 2bc7f351d20b53f1c72c16c4b036e491c478c49a - + * serving.txt -> sixserving.txt (rename, no change, 100% match) + * sevencities.txt -> sevencities.txt (no change) + * sevencities.txt -> songofseven.txt (copy, no change, 100% match) + * + * TODO: add commits with various % changes of copy / rename + */ + +void test_diff_rename__match_oid(void) +{ + const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; + const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + git_tree *old_tree, *new_tree; + git_diff_list *diff; + git_diff_options diffopts = {0}; + git_diff_find_options opts; + diff_expects exp; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + /* Must pass GIT_DIFF_INCLUDE_UNMODIFIED if you expect to emulate + * --find-copies-harder during rename transformion... + */ + memset(&diffopts, 0, sizeof(diffopts)); + diffopts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --no-renames \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(4, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + + /* git diff 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + cl_git_pass(git_diff_find_similar(diff, NULL)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_list_free(diff); + + cl_git_pass(git_diff_tree_to_tree( + &diff, g_repo, old_tree, new_tree, &diffopts)); + + /* git diff --find-copies-harder \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + cl_git_pass(git_diff_find_similar(diff, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNMODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_COPIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_RENAMED]); + + git_diff_list_free(diff); + + git_tree_free(old_tree); + git_tree_free(new_tree); +} diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index c5a0e626e..8e8939976 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -34,15 +34,15 @@ void test_diff_tree__0(void) memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(5, exp.files); - cl_assert_equal_i(2, exp.file_adds); - cl_assert_equal_i(1, exp.file_dels); - cl_assert_equal_i(2, exp.file_mods); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(5, exp.hunks); @@ -56,15 +56,15 @@ void test_diff_tree__0(void) memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, b, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, b, &opts)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(2, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(2, exp.hunks); @@ -111,22 +111,23 @@ void test_diff_tree__options(void) * - git diff [options] 6bab5c79cd5140d0 605812ab7fe421fdd * - mv .git .gitted */ +#define EXPECT_STATUS_ADM(ADDS,DELS,MODS) { 0, ADDS, DELS, MODS, 0, 0, 0, 0, 0 } diff_expects test_expects[] = { /* a vs b tests */ - { 5, 0, 3, 0, 2, 0, 0, 0, 4, 0, 0, 51, 2, 46, 3 }, - { 5, 0, 3, 0, 2, 0, 0, 0, 4, 0, 0, 53, 4, 46, 3 }, - { 5, 0, 0, 3, 2, 0, 0, 0, 4, 0, 0, 52, 3, 3, 46 }, - { 5, 0, 3, 0, 2, 0, 0, 0, 5, 0, 0, 54, 3, 47, 4 }, + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 51, 2, 46, 3 }, + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 4, 0, 0, 53, 4, 46, 3 }, + { 5, 0, EXPECT_STATUS_ADM(0, 3, 2), 4, 0, 0, 52, 3, 3, 46 }, + { 5, 0, EXPECT_STATUS_ADM(3, 0, 2), 5, 0, 0, 54, 3, 47, 4 }, /* c vs d tests */ - { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 22, 9, 10, 3 }, - { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 19, 12, 7, 0 }, - { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, - { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 20, 11, 8, 1 }, - { 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 18, 11, 0, 7 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 22, 9, 10, 3 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 19, 12, 7, 0 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 20, 11, 8, 1 }, + { 1, 0, EXPECT_STATUS_ADM(0, 0, 1), 1, 0, 0, 18, 11, 0, 7 }, { 0 }, }; diff_expects *expected; - int i; + int i, j; g_repo = cl_git_sandbox_init("attr"); @@ -140,18 +141,17 @@ void test_diff_tree__options(void) opts = test_options[i]; if (test_ab_or_cd[i] == 0) - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); else - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, c, d, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, c, d, &opts)); cl_git_pass(git_diff_foreach( diff, &actual, diff_file_fn, diff_hunk_fn, diff_line_fn)); expected = &test_expects[i]; cl_assert_equal_i(actual.files, expected->files); - cl_assert_equal_i(actual.file_adds, expected->file_adds); - cl_assert_equal_i(actual.file_dels, expected->file_dels); - cl_assert_equal_i(actual.file_mods, expected->file_mods); + for (j = GIT_DELTA_UNMODIFIED; j <= GIT_DELTA_TYPECHANGE; ++j) + cl_assert_equal_i(expected->file_status[j], actual.file_status[j]); cl_assert_equal_i(actual.hunks, expected->hunks); cl_assert_equal_i(actual.lines, expected->lines); cl_assert_equal_i(actual.line_ctxt, expected->line_ctxt); @@ -187,15 +187,15 @@ void test_diff_tree__bare(void) memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(2, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(3, exp.hunks); @@ -225,9 +225,9 @@ void test_diff_tree__merge(void) cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); - cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, a, b, &diff1)); + cl_git_pass(git_diff_tree_to_tree(&diff1, g_repo, a, b, NULL)); - cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, c, b, &diff2)); + cl_git_pass(git_diff_tree_to_tree(&diff2, g_repo, c, b, NULL)); git_tree_free(a); git_tree_free(b); @@ -243,9 +243,9 @@ void test_diff_tree__merge(void) diff1, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(6, exp.files); - cl_assert_equal_i(2, exp.file_adds); - cl_assert_equal_i(1, exp.file_dels); - cl_assert_equal_i(3, exp.file_mods); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(6, exp.hunks); @@ -279,7 +279,7 @@ void test_diff_tree__larger_hunks(void) opts.context_lines = 1; opts.interhunk_lines = 0; - cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); num_d = git_diff_num_deltas(diff); for (d = 0; d < num_d; ++d) { diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 3e388ea70..a4dbe37ff 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -26,7 +26,7 @@ void test_diff_workdir__to_index(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -46,11 +46,11 @@ void test_diff_workdir__to_index(void) * - mv .git .gitted */ cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); cl_assert_equal_i(8, exp.hunks); @@ -94,7 +94,7 @@ void test_diff_workdir__to_tree(void) * The results are documented at the bottom of this file in the * long comment entitled "PREPARATION OF TEST DATA". */ - cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -107,11 +107,11 @@ void test_diff_workdir__to_tree(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(14, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(5, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_UNTRACKED]); } /* Since there is no git diff equivalent, let's just assume that the @@ -127,8 +127,8 @@ void test_diff_workdir__to_tree(void) * a workdir to tree diff (even though it is not really). This is what * you would get from "git diff --name-status 26a125ee1bf" */ - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_index_to_tree(&diff, g_repo, a, NULL, &opts)); + cl_git_pass(git_diff_workdir_to_index(&diff2, g_repo, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); @@ -143,11 +143,11 @@ void test_diff_workdir__to_tree(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(15, exp.files); - cl_assert_equal_i(2, exp.file_adds); - cl_assert_equal_i(5, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(3, exp.file_untracked); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); cl_assert_equal_i(11, exp.hunks); @@ -164,8 +164,8 @@ void test_diff_workdir__to_tree(void) /* Again, emulating "git diff <sha>" for testing purposes using * "git diff --name-status 0017bd4ab1ec3" instead. */ - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, b, &diff)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff2)); + cl_git_pass(git_diff_index_to_tree(&diff, g_repo, b, NULL, &opts)); + cl_git_pass(git_diff_workdir_to_index(&diff2, g_repo, NULL, &opts)); cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); @@ -180,11 +180,11 @@ void test_diff_workdir__to_tree(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(16, exp.files); - cl_assert_equal_i(5, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(3, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(3, exp.file_untracked); + cl_assert_equal_i(5, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(3, exp.file_status[GIT_DELTA_UNTRACKED]); cl_assert_equal_i(12, exp.hunks); @@ -216,7 +216,7 @@ void test_diff_workdir__to_index_with_pathspec(void) opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -228,18 +228,18 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); } git_diff_list_free(diff); pathspec = "modified_file"; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -251,18 +251,18 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); } git_diff_list_free(diff); pathspec = "subdir"; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -274,18 +274,18 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(1, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(1, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); } git_diff_list_free(diff); pathspec = "*_deleted"; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -297,11 +297,11 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(2, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(2, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); } git_diff_list_free(diff); @@ -324,7 +324,7 @@ void test_diff_workdir__filemode_changes(void) /* test once with no mods */ - cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -337,7 +337,7 @@ void test_diff_workdir__filemode_changes(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); } @@ -347,7 +347,7 @@ void test_diff_workdir__filemode_changes(void) cl_assert(cl_toggle_filemode("issue_592/a.txt")); - cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -360,7 +360,7 @@ void test_diff_workdir__filemode_changes(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); } @@ -386,14 +386,14 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void) /* test once with no mods */ - cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL)); memset(&exp, 0, sizeof(exp)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); git_diff_list_free(diff); @@ -402,14 +402,14 @@ void test_diff_workdir__filemode_changes_with_filemode_false(void) cl_assert(cl_toggle_filemode("issue_592/a.txt")); - cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, NULL)); memset(&exp, 0, sizeof(exp)); cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); git_diff_list_free(diff); @@ -442,8 +442,8 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - cl_git_pass(git_diff_index_to_tree(g_repo, &opts, tree, &diff_i2t)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff_w2i)); + cl_git_pass(git_diff_index_to_tree(&diff_i2t, g_repo, tree, NULL, &opts)); + cl_git_pass(git_diff_workdir_to_index(&diff_w2i, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -456,9 +456,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.hunks); cl_assert_equal_i(2, exp.lines); cl_assert_equal_i(1, exp.line_ctxt); @@ -477,9 +477,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.hunks); cl_assert_equal_i(3, exp.lines); cl_assert_equal_i(2, exp.line_ctxt); @@ -500,9 +500,9 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.hunks); cl_assert_equal_i(3, exp.lines); cl_assert_equal_i(1, exp.line_ctxt); @@ -529,7 +529,7 @@ void test_diff_workdir__eof_newline_changes(void) opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -542,9 +542,9 @@ void test_diff_workdir__eof_newline_changes(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(0, exp.hunks); cl_assert_equal_i(0, exp.lines); cl_assert_equal_i(0, exp.line_ctxt); @@ -556,7 +556,7 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_append2file("status/current_file", "\n"); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -569,9 +569,9 @@ void test_diff_workdir__eof_newline_changes(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.hunks); cl_assert_equal_i(2, exp.lines); cl_assert_equal_i(1, exp.line_ctxt); @@ -583,7 +583,7 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_rewritefile("status/current_file", "current_file"); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); for (use_iterator = 0; use_iterator <= 1; use_iterator++) { memset(&exp, 0, sizeof(exp)); @@ -596,9 +596,9 @@ void test_diff_workdir__eof_newline_changes(void) diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); cl_assert_equal_i(1, exp.hunks); cl_assert_equal_i(3, exp.lines); cl_assert_equal_i(0, exp.line_ctxt); @@ -699,13 +699,13 @@ void test_diff_workdir__larger_hunks(void) /* okay, this is a bit silly, but oh well */ switch (i) { case 0: - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_git_pass(git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); break; case 1: - cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts)); break; case 2: - cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, b, &diff)); + cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, b, &opts)); break; } @@ -784,7 +784,7 @@ void test_diff_workdir__submodules(void) GIT_DIFF_RECURSE_UNTRACKED_DIRS | GIT_DIFF_INCLUDE_UNTRACKED_CONTENT; - cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + cl_git_pass(git_diff_workdir_to_tree(&diff, g_repo, a, &opts)); /* diff_print(stderr, diff); */ @@ -801,11 +801,11 @@ void test_diff_workdir__submodules(void) cl_assert_equal_i(10, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(9, exp.file_untracked); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(9, exp.file_status[GIT_DELTA_UNTRACKED]); /* the following numbers match "git diff 873585" exactly */ @@ -828,10 +828,13 @@ void test_diff_workdir__cannot_diff_against_a_bare_repository(void) g_repo = cl_git_sandbox_init("testrepo.git"); - cl_assert_equal_i(GIT_EBAREREPO, git_diff_workdir_to_index(g_repo, &opts, &diff)); + cl_assert_equal_i( + GIT_EBAREREPO, git_diff_workdir_to_index(&diff, g_repo, NULL, &opts)); cl_git_pass(git_repository_head_tree(&tree, g_repo)); - cl_assert_equal_i(GIT_EBAREREPO, git_diff_workdir_to_tree(g_repo, &opts, tree, &diff)); + + cl_assert_equal_i( + GIT_EBAREREPO, git_diff_workdir_to_tree(&diff, g_repo, tree, &opts)); git_tree_free(tree); } diff --git a/tests-clar/fetchhead/fetchhead_data.h b/tests-clar/fetchhead/fetchhead_data.h new file mode 100644 index 000000000..71f67be25 --- /dev/null +++ b/tests-clar/fetchhead/fetchhead_data.h @@ -0,0 +1,21 @@ + +#define FETCH_HEAD_WILDCARD_DATA \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\t\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \ + "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \ + "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n" + +#define FETCH_HEAD_NO_MERGE_DATA \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\tnot-for-merge\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" \ + "49322bb17d3acc9146f98c97d078513228bbf3c0\tnot-for-merge\tbranch 'master' of git://github.com/libgit2/TestGitRepository\n" \ + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1\tnot-for-merge\tbranch 'no-parent' of git://github.com/libgit2/TestGitRepository\n" \ + "d96c4e80345534eccee5ac7b07fc7603b56124cb\tnot-for-merge\ttag 'annotated_tag' of git://github.com/libgit2/TestGitRepository\n" \ + "55a1a760df4b86a02094a904dfa511deb5655905\tnot-for-merge\ttag 'blob' of git://github.com/libgit2/TestGitRepository\n" \ + "8f50ba15d49353813cc6e20298002c0d17b0a9ee\tnot-for-merge\ttag 'commit_tree' of git://github.com/libgit2/TestGitRepository\n" + + +#define FETCH_HEAD_EXPLICIT_DATA \ + "0966a434eb1a025db6b71485ab63a3bfbea520b6\t\tbranch 'first-merge' of git://github.com/libgit2/TestGitRepository\n" + diff --git a/tests-clar/fetchhead/network.c b/tests-clar/fetchhead/network.c new file mode 100644 index 000000000..ef9d01bf9 --- /dev/null +++ b/tests-clar/fetchhead/network.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "fetchhead.h" +#include "fetchhead_data.h" +#include "git2/clone.h" + +CL_IN_CATEGORY("network") + +#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository" + +static git_repository *g_repo; + +void test_fetchhead_network__initialize(void) +{ + g_repo = NULL; +} + +static void cleanup_repository(void *path) +{ + if (g_repo) + git_repository_free(g_repo); + cl_fixture_cleanup((const char *)path); +} + + +static void fetchhead_test_clone(void) +{ + cl_set_cleanup(&cleanup_repository, "./test1"); + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test1", NULL, NULL, NULL)); +} + +static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead) +{ + git_remote *remote; + git_buf fetchhead_buf = GIT_BUF_INIT; + int equals = 0; + + cl_git_pass(git_remote_load(&remote, g_repo, "origin")); + git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO); + + if(fetchspec != NULL) + git_remote_set_fetchspec(remote, fetchspec); + + cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH)); + cl_git_pass(git_remote_download(remote, NULL, NULL)); + git_remote_disconnect(remote); + + cl_git_pass(git_remote_update_tips(remote)); + git_remote_free(remote); + + cl_git_pass(git_futils_readbuffer(&fetchhead_buf, + "./test1/.git/FETCH_HEAD")); + + equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0); + + git_buf_free(&fetchhead_buf); + + cl_assert(equals); +} + +void test_fetchhead_network__wildcard_spec(void) +{ + fetchhead_test_clone(); + fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA); +} + +void test_fetchhead_network__explicit_spec(void) +{ + fetchhead_test_clone(); + fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA); +} + +void test_fetchhead_network__no_merges(void) +{ + git_config *config; + + fetchhead_test_clone(); + + cl_git_pass(git_repository_config(&config, g_repo)); + cl_git_pass(git_config_set_string(config, "branch.master.remote", NULL)); + cl_git_pass(git_config_set_string(config, "branch.master.merge", NULL)); + git_config_free(config); + + fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA); +} diff --git a/tests-clar/fetchhead/nonetwork.c b/tests-clar/fetchhead/nonetwork.c new file mode 100644 index 000000000..1de5280a8 --- /dev/null +++ b/tests-clar/fetchhead/nonetwork.c @@ -0,0 +1,96 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "fetchhead.h" +#include "fetchhead_data.h" + +#define DO_LOCAL_TEST 0 + +static git_repository *g_repo; + +void test_fetchhead_nonetwork__initialize(void) +{ + g_repo = NULL; +} + +static void cleanup_repository(void *path) +{ + if (g_repo) + git_repository_free(g_repo); + cl_fixture_cleanup((const char *)path); +} + +void test_fetchhead_nonetwork__write(void) +{ + git_vector fetchhead_vector; + git_fetchhead_ref *fetchhead[6]; + git_oid oid[6]; + git_buf fetchhead_buf = GIT_BUF_INIT; + size_t i; + int equals = 0; + + git_vector_init(&fetchhead_vector, 6, NULL); + + cl_set_cleanup(&cleanup_repository, "./test1"); + + cl_git_pass(git_repository_init(&g_repo, "./test1", 0)); + + cl_git_pass(git_oid_fromstr(&oid[0], + "49322bb17d3acc9146f98c97d078513228bbf3c0")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[0], &oid[0], 1, + "refs/heads/master", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[0])); + + cl_git_pass(git_oid_fromstr(&oid[1], + "0966a434eb1a025db6b71485ab63a3bfbea520b6")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[1], &oid[1], 0, + "refs/heads/first-merge", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[1])); + + cl_git_pass(git_oid_fromstr(&oid[2], + "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[2], &oid[2], 0, + "refs/heads/no-parent", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[2])); + + cl_git_pass(git_oid_fromstr(&oid[3], + "d96c4e80345534eccee5ac7b07fc7603b56124cb")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[3], &oid[3], 0, + "refs/tags/annotated_tag", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[3])); + + cl_git_pass(git_oid_fromstr(&oid[4], + "55a1a760df4b86a02094a904dfa511deb5655905")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[4], &oid[4], 0, + "refs/tags/blob", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[4])); + + cl_git_pass(git_oid_fromstr(&oid[5], + "8f50ba15d49353813cc6e20298002c0d17b0a9ee")); + cl_git_pass(git_fetchhead_ref_create(&fetchhead[5], &oid[5], 0, + "refs/tags/commit_tree", + "git://github.com/libgit2/TestGitRepository")); + cl_git_pass(git_vector_insert(&fetchhead_vector, fetchhead[5])); + + git_fetchhead_write(g_repo, &fetchhead_vector); + + cl_git_pass(git_futils_readbuffer(&fetchhead_buf, + "./test1/.git/FETCH_HEAD")); + + equals = (strcmp(fetchhead_buf.ptr, FETCH_HEAD_WILDCARD_DATA) == 0); + + for (i=0; i < 6; i++) + git_fetchhead_ref_free(fetchhead[i]); + + git_buf_free(&fetchhead_buf); + + git_vector_free(&fetchhead_vector); + + cl_assert(equals); +} + diff --git a/tests-clar/index/conflicts.c b/tests-clar/index/conflicts.c new file mode 100644 index 000000000..e101b1659 --- /dev/null +++ b/tests-clar/index/conflicts.c @@ -0,0 +1,221 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" +#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" +#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" + +#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" +#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" +#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" + +#define TEST_ANCESTOR_OID "f00ff00ff00ff00ff00ff00ff00ff00ff00ff00f" +#define TEST_OUR_OID "b44bb44bb44bb44bb44bb44bb44bb44bb44bb44b" +#define TEST_THEIR_OID "0123456789abcdef0123456789abcdef01234567" + +// Fixture setup and teardown +void test_index_conflicts__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_conflicts__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +void test_index_conflicts__add(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.oid, TEST_OUR_OID); + + their_entry.path = "test-one.txt"; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); +} + +void test_index_conflicts__add_fixes_incorrect_stage(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + our_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.oid, TEST_OUR_OID); + + their_entry.path = "test-one.txt"; + ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.oid, TEST_THEIR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, &our_entry, &their_entry)); + + cl_assert(git_index_entrycount(repo_index) == 11); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert(git_index_entry_stage(conflict_entry[0]) == 1); + cl_assert(git_index_entry_stage(conflict_entry[1]) == 2); + cl_assert(git_index_entry_stage(conflict_entry[2]) == 3); +} + +void test_index_conflicts__get(void) +{ + git_index_entry *conflict_entry[3]; + git_oid oid; + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-one.txt")); + + cl_assert(strcmp(conflict_entry[0]->path, "conflicts-one.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_OUR_OID); + cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_ONE_THEIR_OID); + cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "conflicts-two.txt")); + + cl_assert(strcmp(conflict_entry[0]->path, "conflicts-two.txt") == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&conflict_entry[0]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_OUR_OID); + cl_assert(git_oid_cmp(&conflict_entry[1]->oid, &oid) == 0); + + git_oid_fromstr(&oid, CONFLICTS_TWO_THEIR_OID); + cl_assert(git_oid_cmp(&conflict_entry[2]->oid, &oid) == 0); +} + +void test_index_conflicts__remove(void) +{ + git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-one.txt")); + cl_assert(git_index_entrycount(repo_index) == 5); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-one.txt") != 0); + } + + cl_git_pass(git_index_conflict_remove(repo_index, "conflicts-two.txt")); + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(strcmp(entry->path, "conflicts-two.txt") != 0); + } +} + +void test_index_conflicts__moved_to_reuc(void) +{ + git_index_entry *entry; + size_t i; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_git_mkfile("./mergedrepo/conflicts-one.txt", "new-file\n"); + + cl_git_pass(git_index_add_from_workdir(repo_index, "conflicts-one.txt")); + + cl_assert(git_index_entrycount(repo_index) == 6); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + + if (strcmp(entry->path, "conflicts-one.txt") == 0) + cl_assert(git_index_entry_stage(entry) == 0); + } +} + +void test_index_conflicts__remove_all_conflicts(void) +{ + size_t i; + git_index_entry *entry; + + cl_assert(git_index_entrycount(repo_index) == 8); + + cl_assert_equal_i(true, git_index_has_conflicts(repo_index)); + + git_index_conflict_cleanup(repo_index); + + cl_assert_equal_i(false, git_index_has_conflicts(repo_index)); + + cl_assert(git_index_entrycount(repo_index) == 2); + + for (i = 0; i < git_index_entrycount(repo_index); i++) { + cl_assert(entry = git_index_get_byindex(repo_index, i)); + cl_assert(git_index_entry_stage(entry) == 0); + } +} + +void test_index_conflicts__partial(void) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + git_index_entry *conflict_entry[3]; + + cl_assert(git_index_entrycount(repo_index) == 8); + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "test-one.txt"; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, TEST_ANCESTOR_OID); + + cl_git_pass(git_index_conflict_add(repo_index, &ancestor_entry, NULL, NULL)); + cl_assert(git_index_entrycount(repo_index) == 9); + + cl_git_pass(git_index_conflict_get(&conflict_entry[0], &conflict_entry[1], + &conflict_entry[2], repo_index, "test-one.txt")); + + cl_assert(git_oid_cmp(&ancestor_entry.oid, &conflict_entry[0]->oid) == 0); + cl_assert(conflict_entry[1] == NULL); + cl_assert(conflict_entry[2] == NULL); +} diff --git a/tests-clar/index/filemodes.c b/tests-clar/index/filemodes.c index 75c94e8e7..882d41748 100644 --- a/tests-clar/index/filemodes.c +++ b/tests-clar/index/filemodes.c @@ -25,7 +25,7 @@ void test_index_filemodes__read(void) cl_assert_equal_i(6, git_index_entrycount(index)); for (i = 0; i < 6; ++i) { - git_index_entry *entry = git_index_get(index, i); + git_index_entry *entry = git_index_get_byindex(index, i); cl_assert(entry != NULL); cl_assert(((entry->mode & 0100) ? 1 : 0) == expected[i]); } @@ -56,35 +56,15 @@ static void add_and_check_mode( int pos; git_index_entry *entry; - cl_git_pass(git_index_add(index, filename, 0)); + cl_git_pass(git_index_add_from_workdir(index, filename)); pos = git_index_find(index, filename); cl_assert(pos >= 0); - entry = git_index_get(index, pos); + entry = git_index_get_byindex(index, pos); cl_assert(entry->mode == expect_mode); } -static void append_and_check_mode( - git_index *index, const char *filename, unsigned int expect_mode) -{ - unsigned int before, after; - git_index_entry *entry; - - before = git_index_entrycount(index); - - cl_git_pass(git_index_append(index, filename, 0)); - - after = git_index_entrycount(index); - cl_assert_equal_i(before + 1, after); - - /* bypass git_index_get since that resorts the index */ - entry = (git_index_entry *)git_vector_get(&index->entries, after - 1); - - cl_assert_equal_s(entry->path, filename); - cl_assert(expect_mode == entry->mode); -} - void test_index_filemodes__untrusted(void) { git_config *cfg; @@ -114,23 +94,7 @@ void test_index_filemodes__untrusted(void) replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - /* 5 - append 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 6 - append 0644 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 7 - append 0755 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 8 - append 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 9 - add new 0644 -> expect 0644 */ + /* 5 - add new 0644 -> expect 0644 */ cl_git_write2file("filemodes/new_off", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); @@ -139,7 +103,7 @@ void test_index_filemodes__untrusted(void) * that doesn't support filemodes correctly, so skip it. */ if (can_filemode) { - /* 10 - add 0755 -> expect 0755 */ + /* 6 - add 0755 -> expect 0755 */ cl_git_write2file("filemodes/new_on", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0755); add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); @@ -182,28 +146,12 @@ void test_index_filemodes__trusted(void) replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - /* 5 - append 0644 over existing 0644 -> expect 0644 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.2", 0644); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); - - /* 6 - append 0644 over existing 0755 -> expect 0644 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.2", 0644); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB); - - /* 7 - append 0755 over existing 0644 -> expect 0755 */ - replace_file_with_mode("exec_off", "filemodes/exec_off.3", 0755); - append_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 8 - append 0755 over existing 0755 -> expect 0755 */ - replace_file_with_mode("exec_on", "filemodes/exec_on.3", 0755); - append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); - - /* 9 - add new 0644 -> expect 0644 */ + /* 5 - add new 0644 -> expect 0644 */ cl_git_write2file("filemodes/new_off", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0644); add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); - /* 10 - add 0755 -> expect 0755 */ + /* 6 - add 0755 -> expect 0755 */ cl_git_write2file("filemodes/new_on", "blah", O_WRONLY | O_CREAT | O_TRUNC, 0755); add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); diff --git a/tests-clar/index/inmemory.c b/tests-clar/index/inmemory.c new file mode 100644 index 000000000..c997b965f --- /dev/null +++ b/tests-clar/index/inmemory.c @@ -0,0 +1,22 @@ +#include "clar_libgit2.h" + +void test_index_inmemory__can_create_an_inmemory_index(void) +{ + git_index *index; + + cl_git_pass(git_index_new(&index)); + cl_assert_equal_i(0, git_index_entrycount(index)); + + git_index_free(index); +} + +void test_index_inmemory__cannot_add_from_workdir_to_an_inmemory_index(void) +{ + git_index *index; + + cl_git_pass(git_index_new(&index)); + + cl_assert_equal_i(GIT_ERROR, git_index_add_from_workdir(index, "test.txt")); + + git_index_free(index); +} diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c index 0479332dc..3ae883d18 100644 --- a/tests-clar/index/read_tree.c +++ b/tests-clar/index/read_tree.c @@ -24,19 +24,19 @@ void test_index_read_tree__read_write_involution(void) cl_git_mkfile("./read_tree/abc/d", NULL); cl_git_mkfile("./read_tree/abc_d", NULL); - cl_git_pass(git_index_add(index, "abc-d", 0)); - cl_git_pass(git_index_add(index, "abc_d", 0)); - cl_git_pass(git_index_add(index, "abc/d", 0)); + cl_git_pass(git_index_add_from_workdir(index, "abc-d")); + cl_git_pass(git_index_add_from_workdir(index, "abc_d")); + cl_git_pass(git_index_add_from_workdir(index, "abc/d")); /* write-tree */ - cl_git_pass(git_tree_create_fromindex(&expected, index)); + cl_git_pass(git_index_write_tree(&expected, index)); /* read-tree */ git_tree_lookup(&tree, repo, &expected); - cl_git_pass(git_index_read_tree(index, tree, NULL)); + cl_git_pass(git_index_read_tree(index, tree)); git_tree_free(tree); - cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); + cl_git_pass(git_index_write_tree(&tree_oid, index)); cl_assert(git_oid_cmp(&expected, &tree_oid) == 0); git_index_free(index); diff --git a/tests-clar/index/rename.c b/tests-clar/index/rename.c index eecd257fd..e16ec00c1 100644 --- a/tests-clar/index/rename.c +++ b/tests-clar/index/rename.c @@ -19,28 +19,28 @@ void test_index_rename__single_file(void) cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); /* This should add a new blob to the object database in 'd4/fa8600b4f37d7516bef4816ae2c64dbf029e3a' */ - cl_git_pass(git_index_add(index, "lame.name.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "lame.name.txt")); cl_assert(git_index_entrycount(index) == 1); cl_git_pass(git_oid_fromstr(&expected, "d4fa8600b4f37d7516bef4816ae2c64dbf029e3a")); position = git_index_find(index, "lame.name.txt"); - entry = git_index_get(index, position); + entry = git_index_get_byindex(index, position); cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); /* This removes the entry from the index, but not from the object database */ - cl_git_pass(git_index_remove(index, position)); + cl_git_pass(git_index_remove(index, "lame.name.txt", 0)); cl_assert(git_index_entrycount(index) == 0); p_rename("./rename/lame.name.txt", "./rename/fancy.name.txt"); - cl_git_pass(git_index_add(index, "fancy.name.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "fancy.name.txt")); cl_assert(git_index_entrycount(index) == 1); position = git_index_find(index, "fancy.name.txt"); - entry = git_index_get(index, position); + entry = git_index_get_byindex(index, position); cl_assert(git_oid_cmp(&expected, &entry->oid) == 0); git_index_free(index); diff --git a/tests-clar/index/reuc.c b/tests-clar/index/reuc.c new file mode 100644 index 000000000..c7c394444 --- /dev/null +++ b/tests-clar/index/reuc.c @@ -0,0 +1,236 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +#define ONE_ANCESTOR_OID "478871385b9cd03908c5383acfd568bef023c6b3" +#define ONE_OUR_OID "4458b8bc9e72b6c8755ae456f60e9844d0538d8c" +#define ONE_THEIR_OID "8b72416545c7e761b64cecad4f1686eae4078aa8" + +#define TWO_ANCESTOR_OID "9d81f82fccc7dcd7de7a1ffead1815294c2e092c" +#define TWO_OUR_OID "8f3c06cff9a83757cec40c80bc9bf31a2582bde9" +#define TWO_THEIR_OID "887b153b165d32409c70163e0f734c090f12f673" + +// Fixture setup and teardown +void test_index_reuc__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_reuc__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +void test_index_reuc__read_bypath(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "two.txt")); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "one.txt")); + + cl_assert(strcmp(reuc->path, "one.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__ignore_case(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + int index_caps; + + index_caps = git_index_caps(repo_index); + + index_caps &= ~GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert(!git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + index_caps |= GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_bypath(repo_index, "TWO.txt")); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__read_byindex(void) +{ + const git_index_reuc_entry *reuc; + git_oid oid; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "one.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, ONE_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, ONE_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, ONE_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__updates_existing(void) +{ + const git_index_reuc_entry *reuc; + git_oid ancestor_oid, our_oid, their_oid, oid; + int index_caps; + + git_index_clear(repo_index); + + index_caps = git_index_caps(repo_index); + + index_caps |= GIT_INDEXCAP_IGNORE_CASE; + cl_git_pass(git_index_set_caps(repo_index, index_caps)); + + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_reuc_add(repo_index, "TWO.txt", + 0100644, &our_oid, + 0100644, &their_oid, + 0100644, &ancestor_oid)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "TWO.txt") == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__remove(void) +{ + git_oid oid; + const git_index_reuc_entry *reuc; + + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + cl_git_pass(git_index_reuc_remove(repo_index, 0)); + cl_git_fail(git_index_reuc_remove(repo_index, 1)); + + cl_assert_equal_i(1, git_index_reuc_entrycount(repo_index)); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + + cl_assert(strcmp(reuc->path, "two.txt") == 0); + cl_assert(reuc->mode[0] == 0100644); + cl_assert(reuc->mode[1] == 0100644); + cl_assert(reuc->mode[2] == 0100644); + git_oid_fromstr(&oid, TWO_ANCESTOR_OID); + cl_assert(git_oid_cmp(&reuc->oid[0], &oid) == 0); + git_oid_fromstr(&oid, TWO_OUR_OID); + cl_assert(git_oid_cmp(&reuc->oid[1], &oid) == 0); + git_oid_fromstr(&oid, TWO_THEIR_OID); + cl_assert(git_oid_cmp(&reuc->oid[2], &oid) == 0); +} + +void test_index_reuc__write(void) +{ + git_oid ancestor_oid, our_oid, their_oid; + const git_index_reuc_entry *reuc; + + git_index_clear(repo_index); + + /* Write out of order to ensure sorting is correct */ + git_oid_fromstr(&ancestor_oid, TWO_ANCESTOR_OID); + git_oid_fromstr(&our_oid, TWO_OUR_OID); + git_oid_fromstr(&their_oid, TWO_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "two.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + git_oid_fromstr(&ancestor_oid, ONE_ANCESTOR_OID); + git_oid_fromstr(&our_oid, ONE_OUR_OID); + git_oid_fromstr(&their_oid, ONE_THEIR_OID); + + cl_git_pass(git_index_reuc_add(repo_index, "one.txt", + 0100644, &ancestor_oid, + 0100644, &our_oid, + 0100644, &their_oid)); + + cl_git_pass(git_index_write(repo_index)); + + cl_git_pass(git_index_read(repo_index)); + cl_assert_equal_i(2, git_index_reuc_entrycount(repo_index)); + + /* ensure sort order was round-tripped correct */ + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 0)); + cl_assert(strcmp(reuc->path, "one.txt") == 0); + + cl_assert(reuc = git_index_reuc_get_byindex(repo_index, 1)); + cl_assert(strcmp(reuc->path, "two.txt") == 0); +} + diff --git a/tests-clar/index/stage.c b/tests-clar/index/stage.c new file mode 100644 index 000000000..ba6f1b806 --- /dev/null +++ b/tests-clar/index/stage.c @@ -0,0 +1,60 @@ +#include "clar_libgit2.h" +#include "index.h" +#include "git2/repository.h" + +static git_repository *repo; +static git_index *repo_index; + +#define TEST_REPO_PATH "mergedrepo" +#define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" + +// Fixture setup and teardown +void test_index_stage__initialize(void) +{ + repo = cl_git_sandbox_init("mergedrepo"); + git_repository_index(&repo_index, repo); +} + +void test_index_stage__cleanup(void) +{ + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + + +void test_index_stage__add_always_adds_stage_0(void) +{ + int entry_idx; + git_index_entry *entry; + + cl_git_mkfile("./mergedrepo/new-file.txt", "new-file\n"); + + cl_git_pass(git_index_add_from_workdir(repo_index, "new-file.txt")); + + cl_assert((entry_idx = git_index_find(repo_index, "new-file.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); +} + +void test_index_stage__find_gets_first_stage(void) +{ + int entry_idx; + git_index_entry *entry; + + cl_assert((entry_idx = git_index_find(repo_index, "one.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert((entry_idx = git_index_find(repo_index, "two.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 0); + + cl_assert((entry_idx = git_index_find(repo_index, "conflicts-one.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); + + cl_assert((entry_idx = git_index_find(repo_index, "conflicts-two.txt")) >= 0); + cl_assert((entry = git_index_get_byindex(repo_index, entry_idx)) != NULL); + cl_assert(git_index_entry_stage(entry) == 1); +} + diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index a535d6815..3b71b704d 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -231,16 +231,60 @@ void test_index_tests__add(void) cl_git_pass(git_oid_fromstr(&id1, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* Add the new file to the index */ - cl_git_pass(git_index_add(index, "test.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "test.txt")); /* Wow... it worked! */ cl_assert(git_index_entrycount(index) == 1); - entry = git_index_get(index, 0); + entry = git_index_get_byindex(index, 0); /* And the built-in hashing mechanism worked as expected */ cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + /* Test access by path instead of index */ + cl_assert((entry = git_index_get_bypath(index, "test.txt", 0)) != NULL); + cl_assert(git_oid_cmp(&id1, &entry->oid) == 0); + git_index_free(index); git_repository_free(repo); } +void test_index_tests__add_from_workdir_to_a_bare_repository_returns_EBAREPO(void) +{ + git_repository *bare_repo; + git_index *index; + + cl_git_pass(git_repository_open(&bare_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_repository_index(&index, bare_repo)); + + cl_assert_equal_i(GIT_EBAREREPO, git_index_add_from_workdir(index, "test.txt")); + + git_index_free(index); + git_repository_free(bare_repo); +} + +/* Test that writing an invalid filename fails */ +void test_index_tests__write_invalid_filename(void) +{ + git_repository *repo; + git_index *index; + git_oid expected; + + p_mkdir("read_tree", 0700); + + cl_git_pass(git_repository_init(&repo, "./read_tree", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_assert(git_index_entrycount(index) == 0); + + cl_git_mkfile("./read_tree/.git/hello", NULL); + + cl_git_pass(git_index_add_from_workdir(index, ".git/hello")); + + /* write-tree */ + cl_git_fail(git_index_write_tree(&expected, index)); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("read_tree"); +} diff --git a/tests-clar/network/fetch.c b/tests-clar/network/fetch.c index 5ff7b0af8..d2140b5f4 100644 --- a/tests-clar/network/fetch.c +++ b/tests-clar/network/fetch.c @@ -18,22 +18,24 @@ void test_network_fetch__cleanup(void) static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data) { - refname = refname; - a = a; - b = b; - data = data; + GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data); ++counter; return 0; } +static void progress(const git_transfer_progress *stats, void *payload) +{ + int *bytes_received = (int*)payload; + *bytes_received = stats->received_bytes; +} + static void do_fetch(const char *url, int flag, int n) { git_remote *remote; - git_off_t bytes; - git_indexer_stats stats; git_remote_callbacks callbacks; + int bytes_received = 0; memset(&callbacks, 0, sizeof(git_remote_callbacks)); callbacks.update_tips = update_tips; @@ -43,10 +45,11 @@ static void do_fetch(const char *url, int flag, int n) git_remote_set_callbacks(remote, &callbacks); git_remote_set_autotag(remote, flag); cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH)); - cl_git_pass(git_remote_download(remote, &bytes, &stats)); + cl_git_pass(git_remote_download(remote, progress, &bytes_received)); git_remote_disconnect(remote); cl_git_pass(git_remote_update_tips(remote)); cl_assert_equal_i(counter, n); + cl_assert(bytes_received > 0); git_remote_free(remote); } diff --git a/tests-clar/network/fetchlocal.c b/tests-clar/network/fetchlocal.c new file mode 100644 index 000000000..bff0bb06b --- /dev/null +++ b/tests-clar/network/fetchlocal.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" + +#include "buffer.h" +#include "path.h" +#include "remote.h" + +static void transfer_cb(const git_transfer_progress *stats, void *payload) +{ + int *callcount = (int*)payload; + GIT_UNUSED(stats); + (*callcount)++; +} + +void test_network_fetchlocal__complete(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + + const char *url = cl_git_fixture_url("testrepo.git"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_connect(origin, GIT_DIR_FETCH)); + cl_git_pass(git_remote_download(origin, transfer_cb, &callcount)); + cl_git_pass(git_remote_update_tips(origin)); + + cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL)); + cl_assert_equal_i(18, refnames.count); + cl_assert(callcount > 0); + + git_strarray_free(&refnames); + git_remote_free(origin); + git_repository_free(repo); +} + +void test_network_fetchlocal__partial(void) +{ + git_repository *repo = cl_git_sandbox_init("partial-testrepo"); + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + const char *url; + + cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL)); + cl_assert_equal_i(1, refnames.count); + + url = cl_git_fixture_url("testrepo.git"); + cl_git_pass(git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, url)); + cl_git_pass(git_remote_connect(origin, GIT_DIR_FETCH)); + cl_git_pass(git_remote_download(origin, transfer_cb, &callcount)); + cl_git_pass(git_remote_update_tips(origin)); + + git_strarray_free(&refnames); + + cl_git_pass(git_reference_list(&refnames, repo, GIT_REF_LISTALL)); + cl_assert_equal_i(19, refnames.count); /* 18 remote + 1 local */ + cl_assert(callcount > 0); + + git_strarray_free(&refnames); + git_remote_free(origin); + + cl_git_sandbox_cleanup(); +} diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c index 3ff619748..f7ae83423 100644 --- a/tests-clar/network/remotelocal.c +++ b/tests-clar/network/remotelocal.c @@ -1,5 +1,4 @@ #include "clar_libgit2.h" -#include "transport.h" #include "buffer.h" #include "path.h" #include "posix.h" @@ -8,45 +7,6 @@ static git_repository *repo; static git_buf file_path_buf = GIT_BUF_INIT; static git_remote *remote; -static void build_local_file_url(git_buf *out, const char *fixture) -{ - const char *in_buf; - - git_buf path_buf = GIT_BUF_INIT; - - cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL)); - cl_git_pass(git_buf_puts(out, "file://")); - -#ifdef _MSC_VER - /* - * A FILE uri matches the following format: file://[host]/path - * where "host" can be empty and "path" is an absolute path to the resource. - * - * In this test, no hostname is used, but we have to ensure the leading triple slashes: - * - * *nix: file:///usr/home/... - * Windows: file:///C:/Users/... - */ - cl_git_pass(git_buf_putc(out, '/')); -#endif - - in_buf = git_buf_cstr(&path_buf); - - /* - * A very hacky Url encoding that only takes care of escaping the spaces - */ - while (*in_buf) { - if (*in_buf == ' ') - cl_git_pass(git_buf_puts(out, "%20")); - else - cl_git_pass(git_buf_putc(out, *in_buf)); - - in_buf++; - } - - git_buf_free(&path_buf); -} - void test_network_remotelocal__initialize(void) { cl_git_pass(git_repository_init(&repo, "remotelocal/", 0)); @@ -83,7 +43,7 @@ static int ensure_peeled__cb(git_remote_head *head, void *payload) static void connect_to_local_repository(const char *local_repository) { - build_local_file_url(&file_path_buf, local_repository); + git_buf_sets(&file_path_buf, cl_git_path_url(local_repository)); cl_git_pass(git_remote_new(&remote, repo, NULL, git_buf_cstr(&file_path_buf), NULL)); cl_git_pass(git_remote_connect(remote, GIT_DIR_FETCH)); diff --git a/tests-clar/network/remoterename.c b/tests-clar/network/remoterename.c new file mode 100644 index 000000000..70041f45d --- /dev/null +++ b/tests-clar/network/remoterename.c @@ -0,0 +1,201 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_remote *_remote; +static git_repository *_repo; + +void test_network_remoterename__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); +} + +void test_network_remoterename__cleanup(void) +{ + git_remote_free(_remote); + + cl_git_sandbox_cleanup(); +} + +static int dont_call_me_cb(const char *fetch_refspec, void *payload) +{ + GIT_UNUSED(fetch_refspec); + GIT_UNUSED(payload); + + cl_assert(false); + + return -1; +} + +void test_network_remoterename__renaming_a_remote_moves_related_configuration_section(void) +{ + assert_config_entry_existence(_repo, "remote.test.fetch", true); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", true); +} + +void test_network_remoterename__renaming_a_remote_updates_branch_related_configuration_entries(void) +{ + assert_config_entry_value(_repo, "branch.master.remote", "test"); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "branch.master.remote", "just/renamed"); +} + +void test_network_remoterename__renaming_a_remote_updates_default_fetchrefspec(void) +{ + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/heads/*:refs/remotes/just/renamed/*"); +} + +void test_network_remoterename__renaming_a_remote_without_a_fetchrefspec_doesnt_create_one(void) +{ + git_config *config; + + git_remote_free(_remote); + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_delete(config, "remote.test.fetch")); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); + + assert_config_entry_existence(_repo, "remote.test.fetch", false); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + assert_config_entry_existence(_repo, "remote.just/renamed.fetch", false); +} + +static int ensure_refspecs(const char* refspec_name, void *payload) +{ + int i = 0; + bool found = false; + const char ** exp = (const char **)payload; + + while (exp[i]) { + if (strcmp(exp[i++], refspec_name)) + continue; + + found = true; + break; + } + + cl_assert(found); + + return 0; +} + +void test_network_remoterename__renaming_a_remote_notifies_of_non_default_fetchrefspec(void) +{ + git_config *config; + + char *expected_refspecs[] = { + "+refs/*:refs/*", + NULL + }; + + git_remote_free(_remote); + cl_git_pass(git_repository_config__weakptr(&config, _repo)); + cl_git_pass(git_config_set_string(config, "remote.test.fetch", "+refs/*:refs/*")); + cl_git_pass(git_remote_load(&_remote, _repo, "test")); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", ensure_refspecs, &expected_refspecs)); + + assert_config_entry_value(_repo, "remote.just/renamed.fetch", "+refs/*:refs/*"); +} + +void test_network_remoterename__new_name_can_contain_dots(void) +{ + cl_git_pass(git_remote_rename(_remote, "just.renamed", dont_call_me_cb, NULL)); + cl_assert_equal_s("just.renamed", git_remote_name(_remote)); +} + +void test_network_remoterename__new_name_must_conform_to_reference_naming_conventions(void) +{ + cl_git_fail(git_remote_rename(_remote, "new@{name", dont_call_me_cb, NULL)); +} + +void test_network_remoterename__renamed_name_is_persisted(void) +{ + git_remote *renamed; + git_repository *another_repo; + + cl_git_fail(git_remote_load(&renamed, _repo, "just/renamed")); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + cl_git_pass(git_repository_open(&another_repo, "testrepo.git")); + cl_git_pass(git_remote_load(&renamed, _repo, "just/renamed")); + + git_remote_free(renamed); + git_repository_free(another_repo); +} + +void test_network_remoterename__cannot_overwrite_an_existing_remote(void) +{ + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test", dont_call_me_cb, NULL)); + cl_assert_equal_i(GIT_EEXISTS, git_remote_rename(_remote, "test_with_pushurl", dont_call_me_cb, NULL)); +} + +void test_network_remoterename__renaming_an_inmemory_remote_persists_it(void) +{ + git_remote *remote; + + assert_config_entry_existence(_repo, "remote.durable.url", false); + + cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/durable.git", NULL)); + + assert_config_entry_existence(_repo, "remote.durable.url", false); + + cl_git_pass(git_remote_rename(remote, "durable", dont_call_me_cb, NULL)); + + assert_config_entry_value(_repo, "remote.durable.url", "git://github.com/libgit2/durable.git"); + + git_remote_free(remote); +} + +void test_network_remoterename__renaming_an_inmemory_nameless_remote_notifies_the_inability_to_update_the_fetch_refspec(void) +{ + git_remote *remote; + + char *expected_refspecs[] = { + "+refs/heads/*:refs/remotes/volatile/*", + NULL + }; + + assert_config_entry_existence(_repo, "remote.volatile.url", false); + + cl_git_pass(git_remote_new( + &remote, + _repo, + NULL, + "git://github.com/libgit2/volatile.git", + "+refs/heads/*:refs/remotes/volatile/*")); + + cl_git_pass(git_remote_rename(remote, "durable", ensure_refspecs, &expected_refspecs)); + + git_remote_free(remote); +} + +void test_network_remoterename__renaming_a_remote_moves_the_underlying_reference(void) +{ + git_reference *underlying; + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + git_reference_free(underlying); + + cl_git_pass(git_remote_rename(_remote, "just/renamed", dont_call_me_cb, NULL)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&underlying, _repo, "refs/remotes/test/master")); + cl_git_pass(git_reference_lookup(&underlying, _repo, "refs/remotes/just/renamed/master")); + git_reference_free(underlying); +} diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 91c3e879d..1d58aba75 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -1,7 +1,6 @@ #include "clar_libgit2.h" #include "buffer.h" #include "refspec.h" -#include "transport.h" #include "remote.h" static git_remote *_remote; @@ -10,9 +9,8 @@ static const git_refspec *_refspec; void test_network_remotes__initialize(void) { - cl_fixture_sandbox("testrepo.git"); + _repo = cl_git_sandbox_init("testrepo.git"); - cl_git_pass(git_repository_open(&_repo, "testrepo.git")); cl_git_pass(git_remote_load(&_remote, _repo, "test")); _refspec = git_remote_fetchspec(_remote); @@ -22,8 +20,7 @@ void test_network_remotes__initialize(void) void test_network_remotes__cleanup(void) { git_remote_free(_remote); - git_repository_free(_repo); - cl_fixture_cleanup("testrepo.git"); + cl_git_sandbox_cleanup(); } void test_network_remotes__parsing(void) @@ -73,7 +70,7 @@ void test_network_remotes__parsing_local_path_fails_if_path_not_found(void) void test_network_remotes__supported_transport_methods_are_supported(void) { - cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") ); + cl_assert( git_remote_supported_url("git://github.com/libgit2/libgit2") ); } void test_network_remotes__unsupported_transport_methods_are_unsupported(void) @@ -186,13 +183,13 @@ void test_network_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 3); + cl_assert(list.count == 4); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 4); + cl_assert(list.count == 5); git_strarray_free(&list); git_config_free(cfg); @@ -226,6 +223,29 @@ void test_network_remotes__add(void) cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); } +void test_network_remotes__cannot_add_a_nameless_remote(void) +{ + git_remote *remote; + + cl_git_fail(git_remote_add(&remote, _repo, NULL, "git://github.com/libgit2/libgit2")); + cl_git_fail(git_remote_add(&remote, _repo, "", "git://github.com/libgit2/libgit2")); +} + +void test_network_remotes__cannot_save_a_nameless_remote(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_new(&remote, _repo, NULL, "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_save(remote)); + git_remote_free(remote); + + cl_git_pass(git_remote_new(&remote, _repo, "", "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_save(remote)); + git_remote_free(remote); +} + void test_network_remotes__tagopt(void) { const char *opt; @@ -249,3 +269,11 @@ void test_network_remotes__tagopt(void) git_config_free(cfg); } + +void test_network_remotes__cannot_load_with_an_empty_url(void) +{ + git_remote *remote; + + cl_git_fail(git_remote_load(&remote, _repo, "empty-remote-url")); + cl_assert(giterr_last()->klass == GITERR_INVALID); +} diff --git a/tests-clar/object/blob/write.c b/tests-clar/object/blob/write.c index 87a9e2072..6d4cbab4f 100644 --- a/tests-clar/object/blob/write.c +++ b/tests-clar/object/blob/write.c @@ -49,7 +49,7 @@ void test_object_blob_write__can_create_a_blob_in_a_standard_repo_from_a_absolut assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); git_buf_free(&full_path); - cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); } void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_filepath(void) @@ -65,5 +65,5 @@ void test_object_blob_write__can_create_a_blob_in_a_bare_repo_from_a_absolute_fi assert_blob_creation(ELSEWHERE "/test.txt", git_buf_cstr(&full_path), &git_blob_create_fromdisk); git_buf_free(&full_path); - cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_must_pass(git_futils_rmdir_r(ELSEWHERE, NULL, GIT_RMDIR_REMOVE_FILES)); } diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests-clar/object/commit/commitstagedfile.c index 882fb49ae..eb78cedaa 100644 --- a/tests-clar/object/commit/commitstagedfile.c +++ b/tests-clar/object/commit/commitstagedfile.c @@ -71,9 +71,9 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) */ cl_git_mkfile("treebuilder/test.txt", "test\n"); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "test.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "test.txt")); - entry = git_index_get(index, 0); + entry = git_index_get_byindex(index, 0); cl_assert(git_oid_cmp(&expected_blob_oid, &entry->oid) == 0); @@ -99,7 +99,7 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) /* * Build the tree from the index */ - cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); + cl_git_pass(git_index_write_tree(&tree_oid, index)); cl_assert(git_oid_cmp(&expected_tree_oid, &tree_oid) == 0); @@ -128,68 +128,3 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) git_tree_free(tree); git_index_free(index); } - -void test_object_commit_commitstagedfile__message_prettify(void) -{ - char buffer[100]; - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1); - cl_assert_equal_s(buffer, ""); - cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1); - cl_assert_equal_s(buffer, ""); - - cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0)); - cl_assert_equal_s("Short\n", buffer); - cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1)); - cl_assert_equal_s("Short\n", buffer); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0); - cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n"); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0); - cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n"); - - /* try out overflow */ - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678", - 0) > 0); - cl_assert_equal_s(buffer, - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n", - 0) > 0); - cl_assert_equal_s(buffer, - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); - - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "123456789", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890", - 0)); - cl_git_fail(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x", - 0)); - - cl_assert(git_message_prettify(buffer, sizeof(buffer), - "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" - "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" - "1234567890", - 1) > 0); - - cl_assert(git_message_prettify(NULL, 0, "", 0) == 1); - cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12); - cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15); -} diff --git a/tests-clar/object/message.c b/tests-clar/object/message.c index 43be8b152..7ef6374b3 100644 --- a/tests-clar/object/message.c +++ b/tests-clar/object/message.c @@ -169,3 +169,68 @@ void test_object_message__keep_comments(void) assert_message_prettifying("# comment\n" ttt "\n", "# comment\n" ttt "\n", 0); assert_message_prettifying(ttt "\n" "# comment\n" ttt "\n", ttt "\n" "# comment\n" ttt "\n", 0); } + +void test_object_message__message_prettify(void) +{ + char buffer[100]; + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 0) == 1); + cl_assert_equal_s(buffer, ""); + cl_assert(git_message_prettify(buffer, sizeof(buffer), "", 1) == 1); + cl_assert_equal_s(buffer, ""); + + cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 0)); + cl_assert_equal_s("Short\n", buffer); + cl_assert_equal_i(7, git_message_prettify(buffer, sizeof(buffer), "Short", 1)); + cl_assert_equal_s("Short\n", buffer); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 0) > 0); + cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n# with some comments still in\n"); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), "This is longer\nAnd multiline\n# with some comments still in\n", 1) > 0); + cl_assert_equal_s(buffer, "This is longer\nAnd multiline\n"); + + /* try out overflow */ + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678", + 0) > 0); + cl_assert_equal_s(buffer, + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n", + 0) > 0); + cl_assert_equal_s(buffer, + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "12345678\n"); + + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "123456789", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "123456789\n", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890", + 0)); + cl_git_fail(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890" + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890""x", + 0)); + + cl_assert(git_message_prettify(buffer, sizeof(buffer), + "1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" + "# 1234567890" "1234567890" "1234567890" "1234567890" "1234567890\n" + "1234567890", + 1) > 0); + + cl_assert(git_message_prettify(NULL, 0, "", 0) == 1); + cl_assert(git_message_prettify(NULL, 0, "Short test", 0) == 12); + cl_assert(git_message_prettify(NULL, 0, "Test\n# with\nComments", 1) == 15); +} diff --git a/tests-clar/object/raw/hash.c b/tests-clar/object/raw/hash.c index 4b8b1b74c..f26035e45 100644 --- a/tests-clar/object/raw/hash.c +++ b/tests-clar/object/raw/hash.c @@ -23,25 +23,25 @@ static char *bye_text = "bye world\n"; void test_object_raw_hash__hash_by_blocks(void) { - git_hash_ctx *ctx; + git_hash_ctx ctx; git_oid id1, id2; - cl_assert((ctx = git_hash_new_ctx()) != NULL); + cl_git_pass(git_hash_ctx_init(&ctx)); /* should already be init'd */ - git_hash_update(ctx, hello_text, strlen(hello_text)); - git_hash_final(&id2, ctx); + cl_git_pass(git_hash_update(&ctx, hello_text, strlen(hello_text))); + cl_git_pass(git_hash_final(&id2, &ctx)); cl_git_pass(git_oid_fromstr(&id1, hello_id)); cl_assert(git_oid_cmp(&id1, &id2) == 0); /* reinit should permit reuse */ - git_hash_init(ctx); - git_hash_update(ctx, bye_text, strlen(bye_text)); - git_hash_final(&id2, ctx); + cl_git_pass(git_hash_init(&ctx)); + cl_git_pass(git_hash_update(&ctx, bye_text, strlen(bye_text))); + cl_git_pass(git_hash_final(&id2, &ctx)); cl_git_pass(git_oid_fromstr(&id1, bye_id)); cl_assert(git_oid_cmp(&id1, &id2) == 0); - git_hash_free_ctx(ctx); + git_hash_ctx_cleanup(&ctx); } void test_object_raw_hash__hash_buffer_in_single_call(void) diff --git a/tests-clar/object/raw/short.c b/tests-clar/object/raw/short.c index 14b1ae219..93c79b6a5 100644 --- a/tests-clar/object/raw/short.c +++ b/tests-clar/object/raw/short.c @@ -43,7 +43,7 @@ void test_object_raw_short__oid_shortener_stresstest_git_oid_shorten(void) for (i = 0; i < MAX_OIDS; ++i) { char *oid_text; - sprintf(number_buffer, "%u", (unsigned int)i); + p_snprintf(number_buffer, 16, "%u", (unsigned int)i); git_hash_buf(&oid, number_buffer, strlen(number_buffer)); oid_text = git__malloc(GIT_OID_HEXSZ + 1); diff --git a/tests-clar/object/tag/read.c b/tests-clar/object/tag/read.c index 4dd5cc253..53272e91c 100644 --- a/tests-clar/object/tag/read.c +++ b/tests-clar/object/tag/read.c @@ -7,6 +7,8 @@ static const char *tag2_id = "7b4384978d2493e851f9cca7858815fac9b10980"; static const char *tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; static const char *bad_tag_id = "eda9f45a2a98d4c17a09d681d88569fa4ea91755"; static const char *badly_tagged_commit = "e90810b8df3e80c413d903f631643c716887138d"; +static const char *short_tag_id = "5da7760512a953e3c7c4e47e4392c7a4338fb729"; +static const char *short_tagged_commit = "4a5ed60bafcf4638b7c8356bd4ce1916bfede93c"; static git_repository *g_repo; @@ -36,7 +38,7 @@ void test_object_tag_read__parse(void) cl_git_pass(git_tag_lookup(&tag1, g_repo, &id1)); cl_assert_equal_s(git_tag_name(tag1), "test"); - cl_assert(git_tag_type(tag1) == GIT_OBJ_TAG); + cl_assert(git_tag_target_type(tag1) == GIT_OBJ_TAG); cl_git_pass(git_tag_target((git_object **)&tag2, tag1)); cl_assert(tag2 != NULL); @@ -83,3 +85,34 @@ void test_object_tag_read__parse_without_tagger(void) git_commit_free(commit); git_repository_free(bad_tag_repo); } + +void test_object_tag_read__parse_without_message(void) +{ + // read and parse a tag without a message field + git_repository *short_tag_repo; + git_tag *short_tag; + git_commit *commit; + git_oid id, id_commit; + + // TODO: This is a little messy + cl_git_pass(git_repository_open(&short_tag_repo, cl_fixture("short_tag.git"))); + + git_oid_fromstr(&id, short_tag_id); + git_oid_fromstr(&id_commit, short_tagged_commit); + + cl_git_pass(git_tag_lookup(&short_tag, short_tag_repo, &id)); + cl_assert(short_tag != NULL); + + cl_assert_equal_s(git_tag_name(short_tag), "no_description"); + cl_assert(git_oid_cmp(&id, git_tag_id(short_tag)) == 0); + cl_assert(short_tag->message == NULL); + + cl_git_pass(git_tag_target((git_object **)&commit, short_tag)); + cl_assert(commit != NULL); + + cl_assert(git_oid_cmp(&id_commit, git_commit_id(commit)) == 0); + + git_tag_free(short_tag); + git_commit_free(commit); + git_repository_free(short_tag_repo); +} diff --git a/tests-clar/object/tree/duplicateentries.c b/tests-clar/object/tree/duplicateentries.c new file mode 100644 index 000000000..3052e2926 --- /dev/null +++ b/tests-clar/object/tree/duplicateentries.c @@ -0,0 +1,157 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static git_repository *_repo; + +void test_object_tree_duplicateentries__initialize(void) { + _repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_duplicateentries__cleanup(void) { + cl_git_sandbox_cleanup(); +} + +/* + * $ git show --format=raw refs/heads/dir + * commit 144344043ba4d4a405da03de3844aa829ae8be0e + * tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 + * parent cf80f8de9f1185bf3a05f993f6121880dd0cfbc9 + * author Ben Straub <bstraub@github.com> 1343755506 -0700 + * committer Ben Straub <bstraub@github.com> 1343755506 -0700 + * + * Change a file mode + * + * diff --git a/a/b.txt b/a/b.txt + * old mode 100644 + * new mode 100755 + * + * $ git ls-tree d52a8fe84ceedf260afe4f0287bbfca04a117e83 + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 040000 tree 4e0883eeeeebc1fb1735161cea82f7cb5fab7e63 a + * 100644 blob 45b983be36b73c0788dc9cbcb76cbb80fc7bb057 branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + +static void tree_checker( + git_oid *tid, + const char *expected_sha, + git_filemode_t expected_filemode) +{ + git_tree *tree; + const git_tree_entry *entry; + git_oid oid; + + cl_git_pass(git_tree_lookup(&tree, _repo, tid)); + cl_assert_equal_i(1, git_tree_entrycount(tree)); + entry = git_tree_entry_byindex(tree, 0); + + cl_git_pass(git_oid_fromstr(&oid, expected_sha)); + + cl_assert_equal_i(0, git_oid_cmp(&oid, git_tree_entry_id(entry))); + cl_assert_equal_i(expected_filemode, git_tree_entry_filemode(entry)); + + git_tree_free(tree); +} + +static void tree_creator(git_oid *out, void (*fn)(git_treebuilder *)) +{ + git_treebuilder *builder; + + cl_git_pass(git_treebuilder_create(&builder, NULL)); + + fn(builder); + + cl_git_pass(git_treebuilder_write(out, _repo, builder)); + git_treebuilder_free(builder); +} + +static void two_blobs(git_treebuilder *bld) +{ + git_oid oid; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&oid, + "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); + + cl_git_pass(git_oid_fromstr(&oid, + "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); /* blob oid (new.txt) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); +} + +static void one_blob_and_one_tree(git_treebuilder *bld) +{ + git_oid oid; + const git_tree_entry *entry; + + cl_git_pass(git_oid_fromstr(&oid, + "a8233120f6ad708f843d861ce2b7228ec4e3dec6")); /* blob oid (README) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_BLOB)); + + cl_git_pass(git_oid_fromstr(&oid, + "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63")); /* tree oid (a) */ + + cl_git_pass(git_treebuilder_insert( + &entry, bld, "duplicate", &oid, + GIT_FILEMODE_TREE)); +} + +void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_through_the_treebuilder(void) +{ + git_oid tid; + + tree_creator(&tid, two_blobs); + tree_checker(&tid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd", GIT_FILEMODE_BLOB); + + tree_creator(&tid, one_blob_and_one_tree); + tree_checker(&tid, "4e0883eeeeebc1fb1735161cea82f7cb5fab7e63", GIT_FILEMODE_TREE); +} + +static void add_fake_conflicts(git_index *index) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = "duplicate"; + ancestor_entry.mode = GIT_FILEMODE_BLOB; + ancestor_entry.flags |= (1 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&ancestor_entry.oid, "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + + our_entry.path = "duplicate"; + our_entry.mode = GIT_FILEMODE_BLOB; + ancestor_entry.flags |= (2 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&our_entry.oid, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + + their_entry.path = "duplicate"; + their_entry.mode = GIT_FILEMODE_BLOB; + ancestor_entry.flags |= (3 << GIT_IDXENTRY_STAGESHIFT); + git_oid_fromstr(&their_entry.oid, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"); + + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, &our_entry, &their_entry)); +} + +void test_object_tree_duplicateentries__cannot_create_a_duplicate_entry_building_a_tree_from_a_index_with_conflicts(void) +{ + git_index *index; + git_oid tid; + + cl_git_pass(git_repository_index(&index, _repo)); + + add_fake_conflicts(index); + + cl_assert_equal_i(GIT_EUNMERGED, git_index_write_tree(&tid, index)); + + git_index_free(index); +} diff --git a/tests-clar/object/tree/write.c b/tests-clar/object/tree/write.c index 657bed289..cc5438b05 100644 --- a/tests-clar/object/tree/write.c +++ b/tests-clar/object/tree/write.c @@ -39,6 +39,12 @@ void test_object_tree_write__from_memory(void) &bid, GIT_FILEMODE_BLOB)); cl_git_fail(git_treebuilder_insert(NULL, builder, "/", &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".git", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "..", + &bid, GIT_FILEMODE_BLOB)); + cl_git_fail(git_treebuilder_insert(NULL, builder, ".", + &bid, GIT_FILEMODE_BLOB)); cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", &bid, GIT_FILEMODE_BLOB)); diff --git a/tests-clar/odb/alternates.c b/tests-clar/odb/alternates.c new file mode 100644 index 000000000..785d3bc84 --- /dev/null +++ b/tests-clar/odb/alternates.c @@ -0,0 +1,75 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "repository.h" + +static git_buf destpath, filepath; +static const char *paths[] = { + "A.git", "B.git", "C.git", "D.git", "E.git", "F.git", "G.git" +}; +static git_filebuf file; +static git_repository *repo; + +void test_odb_alternates__cleanup(void) +{ + git_buf_free(&destpath); + git_buf_free(&filepath); +} + +static void init_linked_repo(const char *path, const char *alternate) +{ + git_buf_clear(&destpath); + git_buf_clear(&filepath); + + cl_git_pass(git_repository_init(&repo, path, 1)); + cl_git_pass(git_path_prettify(&destpath, alternate, NULL)); + cl_git_pass(git_buf_joinpath(&destpath, destpath.ptr, "objects")); + cl_git_pass(git_buf_joinpath(&filepath, git_repository_path(repo), "objects/info")); + cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_buf_joinpath(&filepath, filepath.ptr , "alternates")); + + cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0)); + git_filebuf_printf(&file, "%s\n", git_buf_cstr(&destpath)); + cl_git_pass(git_filebuf_commit(&file, 0644)); + + git_repository_free(repo); +} + +void test_odb_alternates__chained(void) +{ + git_commit *commit; + git_oid oid; + + /* Set the alternate A -> testrepo.git */ + init_linked_repo(paths[0], cl_fixture("testrepo.git")); + + /* Set the alternate B -> A */ + init_linked_repo(paths[1], paths[0]); + + /* Now load B and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[1])); + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, repo, &oid)); + git_commit_free(commit); + git_repository_free(repo); +} + +void test_odb_alternates__long_chain(void) +{ + git_commit *commit; + git_oid oid; + size_t i; + + /* Set the alternate A -> testrepo.git */ + init_linked_repo(paths[0], cl_fixture("testrepo.git")); + + /* Set up the five-element chain */ + for (i = 1; i < ARRAY_SIZE(paths); i++) { + init_linked_repo(paths[i], paths[i-1]); + } + + /* Now load the last one and see if we can find an object from testrepo.git */ + cl_git_pass(git_repository_open(&repo, paths[ARRAY_SIZE(paths)-1])); + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_fail(git_commit_lookup(&commit, repo, &oid)); + git_repository_free(repo); +} diff --git a/tests-clar/pack/packbuilder.c b/tests-clar/pack/packbuilder.c index fa7bec14e..208141c27 100644 --- a/tests-clar/pack/packbuilder.c +++ b/tests-clar/pack/packbuilder.c @@ -28,12 +28,12 @@ void test_pack_packbuilder__cleanup(void) git_packbuilder_free(_packbuilder); git_revwalk_free(_revwalker); git_indexer_free(_indexer); + _indexer = NULL; git_repository_free(_repo); } -void test_pack_packbuilder__create_pack(void) +static void seed_packbuilder(void) { - git_indexer_stats stats; git_oid oid, *o; unsigned int i; @@ -58,10 +58,37 @@ void test_pack_packbuilder__create_pack(void) git_commit_tree_oid((git_commit *)obj))); git_object_free(obj); } +} +void test_pack_packbuilder__create_pack(void) +{ + git_transfer_progress stats; + + seed_packbuilder(); cl_git_pass(git_packbuilder_write(_packbuilder, "testpack.pack")); cl_git_pass(git_indexer_new(&_indexer, "testpack.pack")); cl_git_pass(git_indexer_run(_indexer, &stats)); cl_git_pass(git_indexer_write(_indexer)); } + +static git_transfer_progress stats; +static int foreach_cb(void *buf, size_t len, void *payload) +{ + git_indexer_stream *idx = (git_indexer_stream *) payload; + + cl_git_pass(git_indexer_stream_add(idx, buf, len, &stats)); + + return 0; +} + +void test_pack_packbuilder__foreach(void) +{ + git_indexer_stream *idx; + + seed_packbuilder(); + cl_git_pass(git_indexer_stream_new(&idx, ".", NULL, NULL)); + cl_git_pass(git_packbuilder_foreach(_packbuilder, foreach_cb, idx)); + cl_git_pass(git_indexer_stream_finalize(idx, &stats)); + git_indexer_stream_free(idx); +} diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c index 4e9c70904..da7db13fc 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests-clar/refs/branches/delete.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "refs.h" #include "repo/repo_helpers.h" +#include "config/config_helpers.h" static git_repository *repo; static git_reference *fake_remote; @@ -90,3 +91,17 @@ void test_refs_branches_delete__can_delete_a_remote_branch(void) cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); cl_git_pass(git_branch_delete(branch)); } + +void test_refs_branches_delete__deleting_a_branch_removes_related_configuration_data(void) +{ + git_reference *branch; + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); +}
\ No newline at end of file diff --git a/tests-clar/refs/branches/ishead.c b/tests-clar/refs/branches/ishead.c index ab17482b8..52a0a1941 100644 --- a/tests-clar/refs/branches/ishead.c +++ b/tests-clar/refs/branches/ishead.c @@ -39,6 +39,22 @@ void test_refs_branches_ishead__can_properly_handle_orphaned_HEAD(void) repo = NULL; } +void test_refs_branches_ishead__can_properly_handle_missing_HEAD(void) +{ + git_repository_free(repo); + + repo = cl_git_sandbox_init("testrepo.git"); + + delete_head(repo); + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_assert_equal_i(false, git_branch_is_head(branch)); + + cl_git_sandbox_cleanup(); + repo = NULL; +} + void test_refs_branches_ishead__can_tell_if_a_branch_is_not_pointed_at_by_HEAD(void) { cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/br2")); diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 62b6042c6..042469016 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -1,5 +1,6 @@ #include "clar_libgit2.h" #include "refs.h" +#include "config/config_helpers.h" static git_repository *repo; static git_reference *ref; @@ -63,6 +64,27 @@ void test_refs_branches_move__can_force_move_over_an_existing_branch(void) cl_git_pass(git_branch_move(ref, "master", 1)); } +void test_refs_branches_move__moving_a_branch_moves_related_configuration_data(void) +{ + git_reference *branch; + + cl_git_pass(git_branch_lookup(&branch, repo, "track-local", GIT_BRANCH_LOCAL)); + + assert_config_entry_existence(repo, "branch.track-local.remote", true); + assert_config_entry_existence(repo, "branch.track-local.merge", true); + assert_config_entry_existence(repo, "branch.moved.remote", false); + assert_config_entry_existence(repo, "branch.moved.merge", false); + + cl_git_pass(git_branch_move(branch, "moved", 0)); + + assert_config_entry_existence(repo, "branch.track-local.remote", false); + assert_config_entry_existence(repo, "branch.track-local.merge", false); + assert_config_entry_existence(repo, "branch.moved.remote", true); + assert_config_entry_existence(repo, "branch.moved.merge", true); + + git_reference_free(branch); +} + void test_refs_branches_move__moving_the_branch_pointed_at_by_HEAD_updates_HEAD(void) { git_reference *branch; diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c index 86781c041..512e8ba02 100644 --- a/tests-clar/refs/reflog/drop.c +++ b/tests-clar/refs/reflog/drop.c @@ -43,35 +43,28 @@ void test_refs_reflog_drop__can_drop_an_entry(void) void test_refs_reflog_drop__can_drop_an_entry_and_rewrite_the_log_history(void) { - const git_reflog_entry *before_previous, *before_next; - const git_reflog_entry *after_next; - git_oid before_next_old_oid; + const git_reflog_entry *before_current; + const git_reflog_entry *after_current; + git_oid before_current_old_oid, before_current_cur_oid; cl_assert(entrycount > 4); - before_previous = git_reflog_entry_byindex(g_reflog, 3); - before_next = git_reflog_entry_byindex(g_reflog, 1); - git_oid_cpy(&before_next_old_oid, &before_next->oid_old); + before_current = git_reflog_entry_byindex(g_reflog, 1); - cl_git_pass(git_reflog_drop(g_reflog, 2, 1)); - cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + git_oid_cpy(&before_current_old_oid, &before_current->oid_old); + git_oid_cpy(&before_current_cur_oid, &before_current->oid_cur); - after_next = git_reflog_entry_byindex(g_reflog, 1); + cl_git_pass(git_reflog_drop(g_reflog, 1, 1)); - cl_assert_equal_i(0, git_oid_cmp(&before_next->oid_cur, &after_next->oid_cur)); - cl_assert(git_oid_cmp(&before_next_old_oid, &after_next->oid_old) != 0); - cl_assert_equal_i(0, git_oid_cmp(&before_previous->oid_cur, &after_next->oid_old)); -} + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); -void test_refs_reflog_drop__can_drop_the_first_entry(void) -{ - cl_assert(entrycount > 2); + after_current = git_reflog_entry_byindex(g_reflog, 0); - cl_git_pass(git_reflog_drop(g_reflog, 0, 0)); - cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + cl_assert_equal_i(0, git_oid_cmp(&before_current_old_oid, &after_current->oid_old)); + cl_assert(0 != git_oid_cmp(&before_current_cur_oid, &after_current->oid_cur)); } -void test_refs_reflog_drop__can_drop_the_last_entry(void) +void test_refs_reflog_drop__can_drop_the_oldest_entry(void) { const git_reflog_entry *entry; @@ -84,7 +77,7 @@ void test_refs_reflog_drop__can_drop_the_last_entry(void) cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) != 0); } -void test_refs_reflog_drop__can_drop_the_last_entry_and_rewrite_the_log_history(void) +void test_refs_reflog_drop__can_drop_the_oldest_entry_and_rewrite_the_log_history(void) { const git_reflog_entry *entry; @@ -102,8 +95,8 @@ void test_refs_reflog_drop__can_drop_all_the_entries(void) cl_assert(--entrycount > 0); do { - cl_git_pass(git_reflog_drop(g_reflog, --entrycount, 1)); - } while (entrycount > 0); + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); + } while (--entrycount > 0); cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); @@ -117,7 +110,7 @@ void test_refs_reflog_drop__can_persist_deletion_on_disk(void) cl_assert(entrycount > 2); cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); - cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_git_pass(git_reflog_drop(g_reflog, 0, 1)); cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); cl_git_pass(git_reflog_write(g_reflog)); diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c index 20f08f523..09b935692 100644 --- a/tests-clar/refs/reflog/reflog.c +++ b/tests-clar/refs/reflog/reflog.c @@ -68,13 +68,13 @@ void test_refs_reflog_reflog__append_then_read(void) cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); cl_assert_equal_i(2, git_reflog_entrycount(reflog)); - entry = git_reflog_entry_byindex(reflog, 0); + entry = git_reflog_entry_byindex(reflog, 1); assert_signature(committer, entry->committer); cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); cl_assert(entry->msg == NULL); - entry = git_reflog_entry_byindex(reflog, 1); + entry = git_reflog_entry_byindex(reflog, 0); assert_signature(committer, entry->committer); cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0); cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); diff --git a/tests-clar/repo/discover.c b/tests-clar/repo/discover.c index b5afab75a..3d9aeedd7 100644 --- a/tests-clar/repo/discover.c +++ b/tests-clar/repo/discover.c @@ -135,7 +135,7 @@ void test_repo_discover__0(void) ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, sub_repository_path); ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, repository_path); - cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES)); git_repository_free(repo); git_buf_free(&ceiling_dirs_buf); } diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c index ffcd171f2..b372f5b70 100644 --- a/tests-clar/repo/getters.c +++ b/tests-clar/repo/getters.c @@ -1,26 +1,26 @@ #include "clar_libgit2.h" -void test_repo_getters__initialize(void) +void test_repo_getters__is_empty_correctly_deals_with_pristine_looking_repos(void) { - cl_fixture_sandbox("testrepo.git"); -} + git_repository *repo; -void test_repo_getters__cleanup(void) -{ - cl_fixture_cleanup("testrepo.git"); + repo = cl_git_sandbox_init("empty_bare.git"); + cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); + + cl_assert_equal_i(true, git_repository_is_empty(repo)); + + cl_git_sandbox_cleanup(); } -void test_repo_getters__empty(void) +void test_repo_getters__is_empty_can_detect_used_repositories(void) { - git_repository *repo_empty, *repo_normal; + git_repository *repo; - cl_git_pass(git_repository_open(&repo_normal, cl_fixture("testrepo.git"))); - cl_assert(git_repository_is_empty(repo_normal) == 0); - git_repository_free(repo_normal); + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_open(&repo_empty, cl_fixture("empty_bare.git"))); - cl_assert(git_repository_is_empty(repo_empty) == 1); - git_repository_free(repo_empty); + cl_assert_equal_i(false, git_repository_is_empty(repo)); + + git_repository_free(repo); } void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) @@ -28,7 +28,7 @@ void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) git_odb *odb; git_repository *repo; - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); cl_git_pass(git_repository_odb(&odb, repo)); cl_assert(((git_refcount *)odb)->refcount == 2); diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c index 58f525e2b..551e834f2 100644 --- a/tests-clar/repo/head.c +++ b/tests-clar/repo/head.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "refs.h" #include "repo_helpers.h" +#include "posix.h" git_repository *repo; @@ -178,6 +179,15 @@ void test_repo_head__retrieving_an_orphaned_head_returns_GIT_EORPHANEDHEAD(void) cl_assert_equal_i(GIT_EORPHANEDHEAD, git_repository_head(&head, repo)); } +void test_repo_head__retrieving_a_missing_head_returns_GIT_ENOTFOUND(void) +{ + git_reference *head; + + delete_head(repo); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); +} + void test_repo_head__can_tell_if_an_orphaned_head_is_detached(void) { make_head_orphaned(repo, NON_EXISTING_HEAD); diff --git a/tests-clar/repo/open.c b/tests-clar/repo/open.c index ef912fa0e..7f93ae91a 100644 --- a/tests-clar/repo/open.c +++ b/tests-clar/repo/open.c @@ -7,7 +7,7 @@ void test_repo_open__cleanup(void) cl_git_sandbox_cleanup(); if (git_path_isdir("alternate")) - git_futils_rmdir_r("alternate", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS); + git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES); } void test_repo_open__bare_empty_repo(void) @@ -202,8 +202,8 @@ void test_repo_open__bad_gitlinks(void) cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); } - git_futils_rmdir_r("invalid", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS); - git_futils_rmdir_r("invalid2", NULL, GIT_DIRREMOVAL_FILES_AND_DIRS); + git_futils_rmdir_r("invalid", NULL, GIT_RMDIR_REMOVE_FILES); + git_futils_rmdir_r("invalid2", NULL, GIT_RMDIR_REMOVE_FILES); } #ifdef GIT_WIN32 diff --git a/tests-clar/repo/repo_helpers.c b/tests-clar/repo/repo_helpers.c index 35271feaa..19ab38ee3 100644 --- a/tests-clar/repo/repo_helpers.c +++ b/tests-clar/repo/repo_helpers.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "refs.h" #include "repo_helpers.h" +#include "posix.h" void make_head_orphaned(git_repository* repo, const char *target) { @@ -9,3 +10,13 @@ void make_head_orphaned(git_repository* repo, const char *target) cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, target, 1)); git_reference_free(head); } + +void delete_head(git_repository* repo) +{ + git_buf head_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&head_path, git_repository_path(repo), GIT_HEAD_FILE)); + cl_git_pass(p_unlink(git_buf_cstr(&head_path))); + + git_buf_free(&head_path); +} diff --git a/tests-clar/repo/repo_helpers.h b/tests-clar/repo/repo_helpers.h index e6aeb4873..09b5cac84 100644 --- a/tests-clar/repo/repo_helpers.h +++ b/tests-clar/repo/repo_helpers.h @@ -3,3 +3,4 @@ #define NON_EXISTING_HEAD "refs/heads/hide/and/seek" extern void make_head_orphaned(git_repository* repo, const char *target); +extern void delete_head(git_repository* repo); diff --git a/tests-clar/repo/state.c b/tests-clar/repo/state.c new file mode 100644 index 000000000..5a0a5f360 --- /dev/null +++ b/tests-clar/repo/state.c @@ -0,0 +1,96 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refs.h" +#include "posix.h" +#include "fileops.h" + +static git_repository *_repo; +static git_buf _path; + +void test_repo_state__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_state__cleanup(void) +{ + cl_git_sandbox_cleanup(); + git_buf_free(&_path); +} + +static void setup_simple_state(const char *filename) +{ + cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), filename)); + git_futils_mkpath2file(git_buf_cstr(&_path), 0777); + cl_git_mkfile(git_buf_cstr(&_path), "dummy"); +} + +static void assert_repo_state(git_repository_state_t state) +{ + cl_assert_equal_i(state, git_repository_state(_repo)); +} + +void test_repo_state__none_with_HEAD_attached(void) +{ + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__none_with_HEAD_detached(void) +{ + cl_git_pass(git_repository_detach_head(_repo)); + assert_repo_state(GIT_REPOSITORY_STATE_NONE); +} + +void test_repo_state__merge(void) +{ + setup_simple_state(GIT_MERGE_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_MERGE); +} + +void test_repo_state__revert(void) +{ + setup_simple_state(GIT_REVERT_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REVERT); +} + +void test_repo_state__cherry_pick(void) +{ + setup_simple_state(GIT_CHERRY_PICK_HEAD_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_CHERRY_PICK); +} + +void test_repo_state__bisect(void) +{ + setup_simple_state(GIT_BISECT_LOG_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_BISECT); +} + +void test_repo_state__rebase_interactive(void) +{ + setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE); +} + +void test_repo_state__rebase_merge(void) +{ + setup_simple_state(GIT_REBASE_MERGE_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE); +} + +void test_repo_state__rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_REBASE); +} + +void test_repo_state__apply_mailbox(void) +{ + setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX); +} + +void test_repo_state__apply_mailbox_or_rebase(void) +{ + setup_simple_state(GIT_REBASE_APPLY_DIR "whatever"); + assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE); +} diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c index fdab9c536..bddbd17d7 100644 --- a/tests-clar/reset/hard.c +++ b/tests-clar/reset/hard.c @@ -19,30 +19,71 @@ void test_reset_hard__cleanup(void) cl_git_sandbox_cleanup(); } -void test_reset_hard__resetting_culls_empty_directories(void) +static int strequal_ignore_eol(const char *exp, const char *str) { - git_buf subdir_path = GIT_BUF_INIT; - git_buf subfile_path = GIT_BUF_INIT; - git_buf newdir_path = GIT_BUF_INIT; + while (*exp && *str) { + if (*exp != *str) { + while (*exp == '\r' || *exp == '\n') ++exp; + while (*str == '\r' || *str == '\n') ++str; + if (*exp != *str) + return false; + } else { + exp++; str++; + } + } + return (!*exp && !*str); +} - cl_git_pass(git_buf_joinpath(&newdir_path, git_repository_workdir(repo), "newdir/")); +void test_reset_hard__resetting_reverts_modified_files(void) +{ + git_buf path = GIT_BUF_INIT, content = GIT_BUF_INIT; + int i; + static const char *files[4] = { + "current_file", + "modified_file", + "staged_new_file", + "staged_changes_modified_file" }; + static const char *before[4] = { + "current_file\n", + "modified_file\nmodified_file\n", + "staged_new_file\n", + "staged_changes_modified_file\nstaged_changes_modified_file\nstaged_changes_modified_file\n" + }; + static const char *after[4] = { + "current_file\n", + "modified_file\n", + /* wrong value because reset is still slightly incorrect */ + "staged_new_file\n", + /* right value: NULL, */ + "staged_changes_modified_file\n" + }; + const char *wd = git_repository_workdir(repo); - cl_git_pass(git_buf_joinpath(&subfile_path, git_buf_cstr(&newdir_path), "with/nested/file.txt")); - cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&subfile_path), 0755)); - cl_git_mkfile(git_buf_cstr(&subfile_path), "all anew...\n"); + cl_assert(wd); - cl_git_pass(git_buf_joinpath(&subdir_path, git_repository_workdir(repo), "subdir/")); - cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == true); + for (i = 0; i < 4; ++i) { + cl_git_pass(git_buf_joinpath(&path, wd, files[i])); + cl_git_pass(git_futils_readbuffer(&content, path.ptr)); + cl_assert_equal_s(before[i], content.ptr); + } + + retrieve_target_from_oid( + &target, repo, "26a125ee1bfc5df1e1b2e9441bbe63c8a7ae989f"); - retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); - cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == false); - cl_assert(git_path_isdir(git_buf_cstr(&newdir_path)) == false); + for (i = 0; i < 4; ++i) { + cl_git_pass(git_buf_joinpath(&path, wd, files[i])); + if (after[i]) { + cl_git_pass(git_futils_readbuffer(&content, path.ptr)); + cl_assert(strequal_ignore_eol(after[i], content.ptr)); + } else { + cl_assert(!git_path_exists(path.ptr)); + } + } - git_buf_free(&subdir_path); - git_buf_free(&subfile_path); - git_buf_free(&newdir_path); + git_buf_free(&content); + git_buf_free(&path); } void test_reset_hard__cannot_reset_in_a_bare_repository(void) @@ -58,3 +99,38 @@ void test_reset_hard__cannot_reset_in_a_bare_repository(void) git_repository_free(bare); } + +void test_reset_hard__cleans_up_merge(void) +{ + git_buf merge_head_path = GIT_BUF_INIT, + merge_msg_path = GIT_BUF_INIT, + merge_mode_path = GIT_BUF_INIT, + orig_head_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MSG")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "Merge commit 0017bd4ab1ec30440b17bae1680cff124ab5f1f6\n"); + + cl_git_pass(git_buf_joinpath(&merge_msg_path, git_repository_path(repo), "MERGE_MODE")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), ""); + + cl_git_pass(git_buf_joinpath(&orig_head_path, git_repository_path(repo), "ORIG_HEAD")); + cl_git_mkfile(git_buf_cstr(&orig_head_path), "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + + retrieve_target_from_oid(&target, repo, "0017bd4ab1ec30440b17bae1680cff124ab5f1f6"); + cl_git_pass(git_reset(repo, target, GIT_RESET_HARD)); + + cl_assert(!git_path_exists(git_buf_cstr(&merge_head_path))); + cl_assert(!git_path_exists(git_buf_cstr(&merge_msg_path))); + cl_assert(!git_path_exists(git_buf_cstr(&merge_mode_path))); + + cl_assert(git_path_exists(git_buf_cstr(&orig_head_path))); + cl_git_pass(p_unlink(git_buf_cstr(&orig_head_path))); + + git_buf_free(&merge_head_path); + git_buf_free(&merge_msg_path); + git_buf_free(&merge_mode_path); + git_buf_free(&orig_head_path); +} diff --git a/tests-clar/reset/soft.c b/tests-clar/reset/soft.c index 1872baf3b..d51e3f1f1 100644 --- a/tests-clar/reset/soft.c +++ b/tests-clar/reset/soft.c @@ -1,5 +1,7 @@ #include "clar_libgit2.h" +#include "posix.h" #include "reset_helpers.h" +#include "path.h" #include "repo/repo_helpers.h" static git_repository *repo; @@ -110,3 +112,19 @@ void test_reset_soft__resetting_against_an_orphaned_head_repo_makes_the_head_no_ git_reference_free(head); } + +void test_reset_soft__fails_when_merging(void) +{ + git_buf merge_head_path = GIT_BUF_INIT; + + cl_git_pass(git_repository_detach_head(repo)); + cl_git_pass(git_buf_joinpath(&merge_head_path, git_repository_path(repo), "MERGE_HEAD")); + cl_git_mkfile(git_buf_cstr(&merge_head_path), "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef\n"); + + retrieve_target_from_oid(&target, repo, KNOWN_COMMIT_IN_BARE_REPO); + + cl_assert_equal_i(GIT_EUNMERGED, git_reset(repo, target, GIT_RESET_SOFT)); + cl_git_pass(p_unlink(git_buf_cstr(&merge_head_path))); + + git_buf_free(&merge_head_path); +} diff --git a/tests-clar/resources/config/config11 b/tests-clar/resources/config/config11 index 880c94589..7331862a5 100644 --- a/tests-clar/resources/config/config11 +++ b/tests-clar/resources/config/config11 @@ -1,3 +1,5 @@ [remote "fancy"] url = git://github.com/libgit2/libgit2 url = git://git.example.com/libgit2 + + diff --git a/tests-clar/resources/config/config4 b/tests-clar/resources/config/config4 index 741fa0ffd..9dd40419e 100644 --- a/tests-clar/resources/config/config4 +++ b/tests-clar/resources/config/config4 @@ -1,3 +1,5 @@ # A variable name on its own is valid [some.section] variable +# A variable and '=' is accepted, but it's not considered true + variableeq = diff --git a/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG new file mode 100644 index 000000000..1f7391f92 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +master diff --git a/tests-clar/resources/mergedrepo/.gitted/HEAD b/tests-clar/resources/mergedrepo/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD new file mode 100644 index 000000000..a5bdf6e40 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_HEAD @@ -0,0 +1 @@ +e2809157a7766f272e4cfe26e61ef2678a5357ff diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MODE diff --git a/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG new file mode 100644 index 000000000..7c4d1f5a9 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/MERGE_MSG @@ -0,0 +1,5 @@ +Merge branch 'branch' + +Conflicts: + conflicts-one.txt + conflicts-two.txt diff --git a/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD new file mode 100644 index 000000000..13d4d6721 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/ORIG_HEAD @@ -0,0 +1 @@ +3a34580a35add43a4cf361e8e9a30060a905c876 diff --git a/tests-clar/resources/mergedrepo/.gitted/config b/tests-clar/resources/mergedrepo/.gitted/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/mergedrepo/.gitted/description b/tests-clar/resources/mergedrepo/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/mergedrepo/.gitted/index b/tests-clar/resources/mergedrepo/.gitted/index Binary files differnew file mode 100644 index 000000000..3d29f78e7 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/index diff --git a/tests-clar/resources/mergedrepo/.gitted/info/exclude b/tests-clar/resources/mergedrepo/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/HEAD b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD new file mode 100644 index 000000000..a385da67b --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/HEAD @@ -0,0 +1,5 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial +9a05ccb4e0f948de03128e095f39dae6976751c5 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 checkout: moving from master to branch +9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch +e2809157a7766f272e4cfe26e61ef2678a5357ff 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371873 -0500 checkout: moving from branch to master +9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch new file mode 100644 index 000000000..26a5e8dc5 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/branch @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371835 -0500 branch: Created from HEAD +9a05ccb4e0f948de03128e095f39dae6976751c5 e2809157a7766f272e4cfe26e61ef2678a5357ff Edward Thomson <ethomson@edwardthomson.com> 1351371872 -0500 commit: branch diff --git a/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..425f7bd89 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 9a05ccb4e0f948de03128e095f39dae6976751c5 Edward Thomson <ethomson@edwardthomson.com> 1351371828 -0500 commit (initial): initial +9a05ccb4e0f948de03128e095f39dae6976751c5 3a34580a35add43a4cf361e8e9a30060a905c876 Edward Thomson <ethomson@edwardthomson.com> 1351372106 -0500 commit: master diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 Binary files differnew file mode 100644 index 000000000..9232f79d9 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/03/db1d37504ca0c4f7c26d7776b0e28bdea08712 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 Binary files differnew file mode 100644 index 000000000..3e124d9a4 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/17/0efc1023e0ed2390150bb4469c8456b63e8f91 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 Binary files differnew file mode 100644 index 000000000..7bb19c873 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/1f/85ca51b8e0aac893a621b61a9c2661d6aa6d81 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e Binary files differnew file mode 100644 index 000000000..487bcffb1 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/22/0bd62631c8cf7a83ef39c6b94595f00517211e diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 Binary files differnew file mode 100644 index 000000000..2eb3954fc --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/32/d55d59265db86dd690f0a7fc563db43e2bc6a6 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 Binary files differnew file mode 100644 index 000000000..ebe83ccb2 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/38/e2d82b9065a237904af4b780b4d68da6950534 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 new file mode 100644 index 000000000..0d4095ffc --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/3a/34580a35add43a4cf361e8e9a30060a905c876 @@ -0,0 +1,2 @@ +xK +1D]}Dx/O"F2oo<*ZoљuIhhrl"r YT8'#vm0.¨.:.#+9R^nG~[=VjR"IjD۔7|N`
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c Binary files differnew file mode 100644 index 000000000..33389c302 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/44/58b8bc9e72b6c8755ae456f60e9844d0538d8c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 Binary files differnew file mode 100644 index 000000000..5361ea685 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/47/8871385b9cd03908c5383acfd568bef023c6b3 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da Binary files differnew file mode 100644 index 000000000..a60da877c --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/51/6bd85f78061e09ccc714561d7b504672cb52da diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad Binary files differnew file mode 100644 index 000000000..85e84d71e --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/53/c1d95a01f4514b162066fc98564500c96c46ad diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 Binary files differnew file mode 100644 index 000000000..b16b521e6 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/6a/ea5f295304c36144ad6e9247a291b7f8112399 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 Binary files differnew file mode 100644 index 000000000..7c4e85ffb --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/70/68e30a7f0090ae32db35dfa1e4189d8780fcb8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 Binary files differnew file mode 100644 index 000000000..65173fc4d --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/75/938de1e367098b3e9a7b1ec3c4ac4548afffe4 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 Binary files differnew file mode 100644 index 000000000..162fa4455 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/7b/26923aaf452b1977eb08617c59475fb3f74b71 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c Binary files differnew file mode 100644 index 000000000..77a519f55 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/84/af62840be1b1c47b778a8a249f3ff45155038c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda Binary files differnew file mode 100644 index 000000000..f624cd4f1 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/71f7a2ee3addfc4ba39fbd0783c8e738d04cda diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 Binary files differnew file mode 100644 index 000000000..096474c03 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/88/7b153b165d32409c70163e0f734c090f12f673 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a Binary files differnew file mode 100644 index 000000000..a413bc6b0 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8a/ad34cc83733590e74b93d0f7cf00375e2a735a diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 Binary files differnew file mode 100644 index 000000000..3ac8f6018 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/3f43d2402825c200f835ca1762413e386fd0b2 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 Binary files differnew file mode 100644 index 000000000..589a5ae9b --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8b/72416545c7e761b64cecad4f1686eae4078aa8 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 Binary files differnew file mode 100644 index 000000000..6503985e3 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/3c06cff9a83757cec40c80bc9bf31a2582bde9 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 Binary files differnew file mode 100644 index 000000000..2eaa80838 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/8f/fcc405925511824a2240a6d3686aa7f8c7ac50 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 new file mode 100644 index 000000000..7373a80d8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/9a/05ccb4e0f948de03128e095f39dae6976751c5 @@ -0,0 +1 @@ +x
!Dm@c6q##Ay/ ܁:#$ltH:闄*DXhV}
˷n[-K_;Z@JGԈbq3"go@I
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c Binary files differnew file mode 100644 index 000000000..c5a651f97 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/9d/81f82fccc7dcd7de7a1ffead1815294c2e092c diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d Binary files differnew file mode 100644 index 000000000..3e14b5dc8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/b7/cedb8ad4cbb22b6363f9578cbd749797f7ef0d diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 Binary files differnew file mode 100644 index 000000000..a641adc2e --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/d0/1885ea594926eae9ba5b54ad76692af5969f51 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff new file mode 100644 index 000000000..fa86662e0 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/e2/809157a7766f272e4cfe26e61ef2678a5357ff @@ -0,0 +1,3 @@ +xK +1D]t> xNq1(]{Pe
mٍ.S0[Dcd +ŅbMԝCgd@>glX].$!0*zu})/E_<ڪO:WځrơqѤh@mt;;5uZyVo\M
\ No newline at end of file diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 Binary files differnew file mode 100644 index 000000000..c9841c698 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/e6/2cac5c88b9928f2695b934c70efa4285324478 diff --git a/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 Binary files differnew file mode 100644 index 000000000..cd587dbec --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/objects/f7/2784290c151092abf04ce6b875068547f70406 diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch new file mode 100644 index 000000000..a5bdf6e40 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/branch @@ -0,0 +1 @@ +e2809157a7766f272e4cfe26e61ef2678a5357ff diff --git a/tests-clar/resources/mergedrepo/.gitted/refs/heads/master b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master new file mode 100644 index 000000000..13d4d6721 --- /dev/null +++ b/tests-clar/resources/mergedrepo/.gitted/refs/heads/master @@ -0,0 +1 @@ +3a34580a35add43a4cf361e8e9a30060a905c876 diff --git a/tests-clar/resources/mergedrepo/conflicts-one.txt b/tests-clar/resources/mergedrepo/conflicts-one.txt new file mode 100644 index 000000000..8aad34cc8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/conflicts-one.txt @@ -0,0 +1,5 @@ +<<<<<<< HEAD +This is most certainly a conflict! +======= +This is a conflict!!! +>>>>>>> branch diff --git a/tests-clar/resources/mergedrepo/conflicts-two.txt b/tests-clar/resources/mergedrepo/conflicts-two.txt new file mode 100644 index 000000000..e62cac5c8 --- /dev/null +++ b/tests-clar/resources/mergedrepo/conflicts-two.txt @@ -0,0 +1,5 @@ +<<<<<<< HEAD +This is without question another conflict! +======= +This is another conflict!!! +>>>>>>> branch diff --git a/tests-clar/resources/mergedrepo/one.txt b/tests-clar/resources/mergedrepo/one.txt new file mode 100644 index 000000000..75938de1e --- /dev/null +++ b/tests-clar/resources/mergedrepo/one.txt @@ -0,0 +1,10 @@ +This is file one! +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one. +This is file one! diff --git a/tests-clar/resources/mergedrepo/two.txt b/tests-clar/resources/mergedrepo/two.txt new file mode 100644 index 000000000..7b26923aa --- /dev/null +++ b/tests-clar/resources/mergedrepo/two.txt @@ -0,0 +1,12 @@ +This is file two! +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two. +This is file two! diff --git a/tests-clar/resources/partial-testrepo/.gitted/HEAD b/tests-clar/resources/partial-testrepo/.gitted/HEAD new file mode 100644 index 000000000..4bfb9c93f --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/dir diff --git a/tests-clar/resources/partial-testrepo/.gitted/config b/tests-clar/resources/partial-testrepo/.gitted/config new file mode 100644 index 000000000..99abaab97 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true +[branch "dir"] diff --git a/tests-clar/resources/partial-testrepo/.gitted/index b/tests-clar/resources/partial-testrepo/.gitted/index Binary files differnew file mode 100644 index 000000000..4f241f914 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/index diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 Binary files differnew file mode 100644 index 000000000..cedb2a22e --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e Binary files differnew file mode 100644 index 000000000..b7d944fa1 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 Binary files differnew file mode 100644 index 000000000..d37b93e4f --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 Binary files differnew file mode 100644 index 000000000..93a16f146 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd b/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd Binary files differnew file mode 100644 index 000000000..ba0bfb30c --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/18/10dff58d8a660512d4832e740f692884338ccd diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 Binary files differnew file mode 100644 index 000000000..7ca4ceed5 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 new file mode 100644 index 000000000..8953b6cef --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 @@ -0,0 +1,2 @@ +xQ +0D)6ͦ "xO-FbEo0Ǥ,ske[Pn8R,EpD?g}^3<GhYK8ЖDA);gݧjp4-r;sGA4ۺ=(in7IKFE
\ No newline at end of file diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 Binary files differnew file mode 100644 index 000000000..e9150214b --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 new file mode 100644 index 000000000..c1f22c54f --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 @@ -0,0 +1,2 @@ +x 1ENi@k2 "X$YW0YcÅszMD08!sXgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/> +F-
\ No newline at end of file diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc Binary files differnew file mode 100644 index 000000000..b669961d8 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 Binary files differnew file mode 100644 index 000000000..9ff5eb2b5 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a Binary files differnew file mode 100644 index 000000000..2ef4faa0f --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d Binary files differnew file mode 100644 index 000000000..2f9b6b6e3 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/81/4889a078c031f61ed08ab5fa863aea9314344d diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 Binary files differnew file mode 100644 index 000000000..5df58dda5 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/84/96071c1b46c854b31185ea97743be6a8774479 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a b/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a new file mode 100644 index 000000000..a79612435 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/9f/d738e8f7967c078dceed8190330fc8648ee56a @@ -0,0 +1,3 @@ +x[ +0E*fդ "W0-Ft݁pS[Yx^ +Db CLhut}8X*4ZsYUA
X3RM) s6輢Mរ&Jm;}<\@ޏpĀv?jۺL?H
\ No newline at end of file diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f b/tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f new file mode 100644 index 000000000..f8588696b --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/a4/a7dce85cf63874e984719f4fdd239f5145052f @@ -0,0 +1,2 @@ +x;j1Dmdǎ|M3`V{>QvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵIKNiZ%S + U~̽>' w
[DGڡQ-M>dO}\8g_ШoYr
\ No newline at end of file diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd Binary files differnew file mode 100644 index 000000000..d0d7e736e --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 Binary files differnew file mode 100644 index 000000000..18a7f61c2 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd b/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd new file mode 100644 index 000000000..75f541f10 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/c4/7800c7266a2be04c571c04d5a6614691ea99bd @@ -0,0 +1,3 @@ +xQ +0D)ʦI<'lR+FjEo0<xha ]șXUlPF)z4y,\r 'S-mI4 +Xh&F}n+\Y-p|鷜oUz;-alt{?I,:oRcHK
\ No newline at end of file diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 Binary files differnew file mode 100644 index 000000000..7620c514f --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 Binary files differnew file mode 100644 index 000000000..00940f0f2 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 Binary files differnew file mode 100644 index 000000000..03770969a --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 Binary files differnew file mode 100644 index 000000000..112998d42 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/fa/49b077972391ad58037050f2a75f74e3671e92 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 b/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 Binary files differnew file mode 100644 index 000000000..12bf5f3e3 --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/fd/093bff70906175335656e6ce6ae05783708765 diff --git a/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep b/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/objects/pack/.gitkeep diff --git a/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir b/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir new file mode 100644 index 000000000..4567d37fa --- /dev/null +++ b/tests-clar/resources/partial-testrepo/.gitted/refs/heads/dir @@ -0,0 +1 @@ +144344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/renames/.gitted/HEAD b/tests-clar/resources/renames/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/renames/.gitted/config b/tests-clar/resources/renames/.gitted/config new file mode 100644 index 000000000..bb4d11c1f --- /dev/null +++ b/tests-clar/resources/renames/.gitted/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = false diff --git a/tests-clar/resources/renames/.gitted/description b/tests-clar/resources/renames/.gitted/description new file mode 100644 index 000000000..498b267a8 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/renames/.gitted/index b/tests-clar/resources/renames/.gitted/index Binary files differnew file mode 100644 index 000000000..1fc69fcbe --- /dev/null +++ b/tests-clar/resources/renames/.gitted/index diff --git a/tests-clar/resources/renames/.gitted/info/exclude b/tests-clar/resources/renames/.gitted/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/tests-clar/resources/renames/.gitted/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/tests-clar/resources/renames/.gitted/logs/HEAD b/tests-clar/resources/renames/.gitted/logs/HEAD new file mode 100644 index 000000000..34222ed7d --- /dev/null +++ b/tests-clar/resources/renames/.gitted/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 Russell Belfer <rb@github.com> 1351024687 -0700 commit (initial): Initial commit +31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 2bc7f351d20b53f1c72c16c4b036e491c478c49a Russell Belfer <rb@github.com> 1351024817 -0700 commit: copy and rename with no change diff --git a/tests-clar/resources/renames/.gitted/logs/refs/heads/master b/tests-clar/resources/renames/.gitted/logs/refs/heads/master new file mode 100644 index 000000000..34222ed7d --- /dev/null +++ b/tests-clar/resources/renames/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 Russell Belfer <rb@github.com> 1351024687 -0700 commit (initial): Initial commit +31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 2bc7f351d20b53f1c72c16c4b036e491c478c49a Russell Belfer <rb@github.com> 1351024817 -0700 commit: copy and rename with no change diff --git a/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 b/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 Binary files differnew file mode 100644 index 000000000..2ee86444d --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/03/da7ad872536bd448da8d88eb7165338bf923a7 diff --git a/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a b/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a Binary files differnew file mode 100644 index 000000000..93f1ccb3f --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/2b/c7f351d20b53f1c72c16c4b036e491c478c49a diff --git a/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 b/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 Binary files differnew file mode 100644 index 000000000..00ce53212 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/31/e47d8c1fa36d7f8d537b96158e3f024de0a9f2 diff --git a/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 b/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 Binary files differnew file mode 100644 index 000000000..24eac54c5 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/61/8c6f2f8740bd6049b2fb9eb93fc15726462745 diff --git a/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba b/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba Binary files differnew file mode 100644 index 000000000..5ee28a76a --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/66/311f5cfbe7836c27510a3ba2f43e282e2c8bba diff --git a/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 b/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 Binary files differnew file mode 100644 index 000000000..440b7bec3 --- /dev/null +++ b/tests-clar/resources/renames/.gitted/objects/ad/0a8e55a104ac54a8a29ed4b84b49e76837a113 diff --git a/tests-clar/resources/renames/.gitted/refs/heads/master b/tests-clar/resources/renames/.gitted/refs/heads/master new file mode 100644 index 000000000..049b1f5ad --- /dev/null +++ b/tests-clar/resources/renames/.gitted/refs/heads/master @@ -0,0 +1 @@ +2bc7f351d20b53f1c72c16c4b036e491c478c49a diff --git a/tests-clar/resources/renames/sevencities.txt b/tests-clar/resources/renames/sevencities.txt new file mode 100644 index 000000000..66311f5cf --- /dev/null +++ b/tests-clar/resources/renames/sevencities.txt @@ -0,0 +1,49 @@ +The Song of Seven Cities +======================== + +I WAS Lord of Cities very sumptuously builded. +Seven roaring Cities paid me tribute from afar. +Ivory their outposts were—the guardrooms of them gilded, +And garrisoned with Amazons invincible in war. + +All the world went softly when it walked before my Cities— +Neither King nor Army vexed my peoples at their toil, +Never horse nor chariot irked or overbore my Cities, +Never Mob nor Ruler questioned whence they drew their spoil. + +Banded, mailed and arrogant from sunrise unto sunset; +Singing while they sacked it, they possessed the land at large. +Yet when men would rob them, they resisted, they made onset +And pierced the smoke of battle with a thousand-sabred charge. + +So they warred and trafficked only yesterday, my Cities. +To-day there is no mark or mound of where my Cities stood. +For the River rose at midnight and it washed away my Cities. +They are evened with Atlantis and the towns before the Flood. + +Rain on rain-gorged channels raised the water-levels round them, +Freshet backed on freshet swelled and swept their world from sight, +Till the emboldened floods linked arms and, flashing forward, drowned them— +Drowned my Seven Cities and their peoples in one night! + +Low among the alders lie their derelict foundations, +The beams wherein they trusted and the plinths whereon they built— +My rulers and their treasure and their unborn populations, +Dead, destroyed, aborted, and defiled with mud and silt! + +The Daughters of the Palace whom they cherished in my Cities, +My silver-tongued Princesses, and the promise of their May— +Their bridegrooms of the June-tide—all have perished in my Cities, +With the harsh envenomed virgins that can neither love nor play. + +I was Lord of Cities—I will build anew my Cities, +Seven, set on rocks, above the wrath of any flood. +Nor will I rest from search till I have filled anew my Cities +With peoples undefeated of the dark, enduring blood. + +To the sound of trumpets shall their seed restore my Cities +Wealthy and well-weaponed, that once more may I behold +All the world go softly when it walks before my Cities, +And the horses and the chariots fleeing from them as of old! + + -- Rudyard Kipling diff --git a/tests-clar/resources/renames/sixserving.txt b/tests-clar/resources/renames/sixserving.txt new file mode 100644 index 000000000..ad0a8e55a --- /dev/null +++ b/tests-clar/resources/renames/sixserving.txt @@ -0,0 +1,24 @@ +I KEEP six honest serving-men + (They taught me all I knew); +Their names are What and Why and When + And How and Where and Who. +I send them over land and sea, + I send them east and west; +But after they have worked for me, + I give them all a rest. + +I let them rest from nine till five, + For I am busy then, +As well as breakfast, lunch, and tea, + For they are hungry men. +But different folk have different views; +I know a person small— +She keeps ten million serving-men, +Who get no rest at all! + +She sends'em abroad on her own affairs, + From the second she opens her eyes— +One million Hows, two million Wheres, +And seven million Whys! + + -- Rudyard Kipling diff --git a/tests-clar/resources/renames/songofseven.txt b/tests-clar/resources/renames/songofseven.txt new file mode 100644 index 000000000..66311f5cf --- /dev/null +++ b/tests-clar/resources/renames/songofseven.txt @@ -0,0 +1,49 @@ +The Song of Seven Cities +======================== + +I WAS Lord of Cities very sumptuously builded. +Seven roaring Cities paid me tribute from afar. +Ivory their outposts were—the guardrooms of them gilded, +And garrisoned with Amazons invincible in war. + +All the world went softly when it walked before my Cities— +Neither King nor Army vexed my peoples at their toil, +Never horse nor chariot irked or overbore my Cities, +Never Mob nor Ruler questioned whence they drew their spoil. + +Banded, mailed and arrogant from sunrise unto sunset; +Singing while they sacked it, they possessed the land at large. +Yet when men would rob them, they resisted, they made onset +And pierced the smoke of battle with a thousand-sabred charge. + +So they warred and trafficked only yesterday, my Cities. +To-day there is no mark or mound of where my Cities stood. +For the River rose at midnight and it washed away my Cities. +They are evened with Atlantis and the towns before the Flood. + +Rain on rain-gorged channels raised the water-levels round them, +Freshet backed on freshet swelled and swept their world from sight, +Till the emboldened floods linked arms and, flashing forward, drowned them— +Drowned my Seven Cities and their peoples in one night! + +Low among the alders lie their derelict foundations, +The beams wherein they trusted and the plinths whereon they built— +My rulers and their treasure and their unborn populations, +Dead, destroyed, aborted, and defiled with mud and silt! + +The Daughters of the Palace whom they cherished in my Cities, +My silver-tongued Princesses, and the promise of their May— +Their bridegrooms of the June-tide—all have perished in my Cities, +With the harsh envenomed virgins that can neither love nor play. + +I was Lord of Cities—I will build anew my Cities, +Seven, set on rocks, above the wrath of any flood. +Nor will I rest from search till I have filled anew my Cities +With peoples undefeated of the dark, enduring blood. + +To the sound of trumpets shall their seed restore my Cities +Wealthy and well-weaponed, that once more may I behold +All the world go softly when it walks before my Cities, +And the horses and the chariots fleeing from them as of old! + + -- Rudyard Kipling diff --git a/tests-clar/resources/short_tag.git/HEAD b/tests-clar/resources/short_tag.git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/short_tag.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/short_tag.git/config b/tests-clar/resources/short_tag.git/config new file mode 100644 index 000000000..a4ef456cb --- /dev/null +++ b/tests-clar/resources/short_tag.git/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + logallrefupdates = true diff --git a/tests-clar/resources/short_tag.git/index b/tests-clar/resources/short_tag.git/index Binary files differnew file mode 100644 index 000000000..87fef7847 --- /dev/null +++ b/tests-clar/resources/short_tag.git/index diff --git a/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c b/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c Binary files differnew file mode 100644 index 000000000..aeb4e4b0b --- /dev/null +++ b/tests-clar/resources/short_tag.git/objects/4a/5ed60bafcf4638b7c8356bd4ce1916bfede93c diff --git a/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c b/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c Binary files differnew file mode 100644 index 000000000..806ce71a5 --- /dev/null +++ b/tests-clar/resources/short_tag.git/objects/4d/5fcadc293a348e88f777dc0920f11e7d71441c diff --git a/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 b/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 new file mode 100644 index 000000000..1192707c9 --- /dev/null +++ b/tests-clar/resources/short_tag.git/objects/5d/a7760512a953e3c7c4e47e4392c7a4338fb729 @@ -0,0 +1 @@ +xM0@aלb. i%1ƍpc@--6&B
E+pVСSƆd/m(RJ%
R^vʩ,Giǖ<Ӵ3\ncinRSg u1
\ No newline at end of file diff --git a/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 Binary files differnew file mode 100644 index 000000000..711223894 --- /dev/null +++ b/tests-clar/resources/short_tag.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests-clar/resources/short_tag.git/packed-refs b/tests-clar/resources/short_tag.git/packed-refs new file mode 100644 index 000000000..ca5197e3c --- /dev/null +++ b/tests-clar/resources/short_tag.git/packed-refs @@ -0,0 +1 @@ +5da7760512a953e3c7c4e47e4392c7a4338fb729 refs/tags/no_description diff --git a/tests-clar/resources/short_tag.git/refs/heads/master b/tests-clar/resources/short_tag.git/refs/heads/master new file mode 100644 index 000000000..fcefd1ef0 --- /dev/null +++ b/tests-clar/resources/short_tag.git/refs/heads/master @@ -0,0 +1 @@ +4a5ed60bafcf4638b7c8356bd4ce1916bfede93c diff --git a/tests-clar/resources/testrepo.git/config b/tests-clar/resources/testrepo.git/config index 54ff6109b..3801ce08d 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -8,6 +8,8 @@ fetch = +refs/heads/*:refs/remotes/test/* [remote "joshaber"] url = git://github.com/libgit2/libgit2 +[remote "empty-remote-url"] + url = [remote "test_with_pushurl"] url = git://github.com/libgit2/fetchlibgit2 diff --git a/tests-clar/stash/drop.c b/tests-clar/stash/drop.c new file mode 100644 index 000000000..b4f73b995 --- /dev/null +++ b/tests-clar/stash/drop.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +static git_repository *repo; +static git_signature *signature; + +void test_stash_drop__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ +} + +void test_stash_drop__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_stash_drop__cannot_drop_from_an_empty_stash(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +static void push_three_states(void) +{ + git_oid oid; + git_index *index; + + cl_git_mkfile("stash/zero.txt", "content\n"); + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_from_workdir(index, "zero.txt")); + commit_staged_files(&oid, index, signature); + + cl_git_mkfile("stash/one.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "First", GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_mkfile("stash/two.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "Second", GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_mkfile("stash/three.txt", "content\n"); + cl_git_pass(git_stash_save(&oid, repo, signature, "Third", GIT_STASH_INCLUDE_UNTRACKED)); + + git_index_free(index); +} + +void test_stash_drop__cannot_drop_a_non_existing_stashed_state(void) +{ + push_three_states(); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 666)); + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 42)); + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 3)); +} + +void test_stash_drop__can_purge_the_stash_from_the_top(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +void test_stash_drop__can_purge_the_stash_from_the_bottom(void) +{ + push_three_states(); + + cl_git_pass(git_stash_drop(repo, 2)); + cl_git_pass(git_stash_drop(repo, 1)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_stash_drop(repo, 0)); +} + +void test_stash_drop__dropping_an_entry_rewrites_reflog_history(void) +{ + git_reference *stash; + git_reflog *reflog; + const git_reflog_entry *entry; + git_oid oid; + size_t count; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash")); + + cl_git_pass(git_reflog_read(&reflog, stash)); + entry = git_reflog_entry_byindex(reflog, 1); + + git_oid_cpy(&oid, git_reflog_entry_oidold(entry)); + count = git_reflog_entrycount(reflog); + + git_reflog_free(reflog); + + cl_git_pass(git_stash_drop(repo, 1)); + + cl_git_pass(git_reflog_read(&reflog, stash)); + entry = git_reflog_entry_byindex(reflog, 0); + + cl_assert_equal_i(0, git_oid_cmp(&oid, git_reflog_entry_oidold(entry))); + cl_assert_equal_i(count - 1, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + + git_reference_free(stash); +} + +void test_stash_drop__dropping_the_last_entry_removes_the_stash(void) +{ + git_reference *stash; + + push_three_states(); + + cl_git_pass(git_reference_lookup(&stash, repo, "refs/stash")); + git_reference_free(stash); + + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + cl_git_pass(git_stash_drop(repo, 0)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&stash, repo, "refs/stash")); +} diff --git a/tests-clar/stash/foreach.c b/tests-clar/stash/foreach.c new file mode 100644 index 000000000..d7127a9db --- /dev/null +++ b/tests-clar/stash/foreach.c @@ -0,0 +1,120 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +struct callback_data +{ + char **oids; + int invokes; +}; + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; +struct callback_data data; + +#define REPO_NAME "stash" + +void test_stash_foreach__initialize(void) +{ + cl_git_pass(git_signature_new( + &signature, + "nulltoken", + "emeric.fermas@gmail.com", + 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + memset(&data, 0, sizeof(struct callback_data)); +} + +void test_stash_foreach__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r(REPO_NAME, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +static int callback_cb( + size_t index, + const char* message, + const git_oid *stash_oid, + void *payload) +{ + struct callback_data *data = (struct callback_data *)payload; + + GIT_UNUSED(index); + GIT_UNUSED(message); + + cl_assert_equal_i(0, git_oid_streq(stash_oid, data->oids[data->invokes++])); + + return 0; +} + +void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void) +{ + char *oids[] = { NULL }; + + data.oids = oids; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + + cl_assert_equal_i(0, data.invokes); +} + +void test_stash_foreach__can_enumerate_a_repository(void) +{ + char *oids_default[] = { + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + char *oids_untracked[] = { + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + char *oids_ignored[] = { + "c95599a8fef20a7e57582c6727b1a0d02e0a5828", + "7f89a8b15c878809c5c54d1ff8f8c9674154017b", + "1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; + + cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); + + setup_stash(repo, signature); + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_DEFAULT)); + + data.oids = oids_default; + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(1, data.invokes); + + data.oids = oids_untracked; + data.invokes = 0; + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_UNTRACKED)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(2, data.invokes); + + data.oids = oids_ignored; + data.invokes = 0; + + cl_git_pass(git_stash_save( + &stash_tip_oid, + repo, + signature, + NULL, + GIT_STASH_INCLUDE_IGNORED)); + + cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); + cl_assert_equal_i(3, data.invokes); +} diff --git a/tests-clar/stash/save.c b/tests-clar/stash/save.c new file mode 100644 index 000000000..fadb8940b --- /dev/null +++ b/tests-clar/stash/save.c @@ -0,0 +1,370 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +static git_repository *repo; +static git_signature *signature; +static git_oid stash_tip_oid; + +/* + * Friendly reminder, in order to ease the reading of the following tests: + * + * "stash" points to the worktree commit + * "stash^1" points to the base commit (HEAD when the stash was created) + * "stash^2" points to the index commit + * "stash^3" points to the untracked commit + */ + +void test_stash_save__initialize(void) +{ + cl_git_pass(git_repository_init(&repo, "stash", 0)); + cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */ + + setup_stash(repo, signature); +} + +void test_stash_save__cleanup(void) +{ + git_signature_free(signature); + git_repository_free(repo); + cl_git_pass(git_futils_rmdir_r("stash", NULL, GIT_RMDIR_REMOVE_FILES)); +} + +static void assert_object_oid(const char* revision, const char* expected_oid, git_otype type) +{ + git_object *object; + int result; + + result = git_revparse_single(&object, repo, revision); + + if (!expected_oid) { + cl_assert_equal_i(GIT_ENOTFOUND, result); + return; + } else + cl_assert_equal_i(0, result); + + cl_assert_equal_i(type, git_object_type(object)); + cl_git_pass(git_oid_streq(git_object_id(object), expected_oid)); + + git_object_free(object); +} + +static void assert_blob_oid(const char* revision, const char* expected_oid) +{ + assert_object_oid(revision, expected_oid, GIT_OBJ_BLOB); +} + +void test_stash_save__does_not_keep_index_by_default(void) +{ +/* +$ git stash + +$ git show refs/stash:what +see you later + +$ git show refs/stash:how +not so small and + +$ git show refs/stash:who +funky world + +$ git show refs/stash:when +fatal: Path 'when' exists on disk, but not in 'stash'. + +$ git show refs/stash^2:what +goodbye + +$ git show refs/stash^2:how +not so small and + +$ git show refs/stash^2:who +world + +$ git show refs/stash^2:when +fatal: Path 'when' exists on disk, but not in 'stash^2'. + +$ git status --short +?? when + +*/ + unsigned int status; + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + cl_git_pass(git_status_file(&status, repo, "when")); + + assert_blob_oid("refs/stash:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("refs/stash:when", NULL); + assert_blob_oid("refs/stash:just.ignore", NULL); + + assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("refs/stash^2:when", NULL); + assert_blob_oid("refs/stash^2:just.ignore", NULL); + + assert_blob_oid("refs/stash^3", NULL); + + cl_assert_equal_i(GIT_STATUS_WT_NEW, status); +} + +static void assert_status( + const char *path, + int status_flags) +{ + unsigned int status; + int error; + + error = git_status_file(&status, repo, path); + + if (status_flags < 0) { + cl_assert_equal_i(status_flags, error); + return; + } + + cl_assert_equal_i(0, error); + cl_assert_equal_i((unsigned int)status_flags, status); +} + +void test_stash_save__can_keep_index(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_KEEP_INDEX)); + + assert_status("what", GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); +} + +static void assert_commit_message_contains(const char *revision, const char *fragment) +{ + git_commit *commit; + + cl_git_pass(git_revparse_single(((git_object **)&commit), repo, revision)); + + cl_assert(strstr(git_commit_message(commit), fragment) != NULL); + + git_commit_free(commit); +} + +void test_stash_save__can_include_untracked_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", NULL); +} + +void test_stash_save__can_include_untracked_and_ignored_files(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED | GIT_STASH_INCLUDE_IGNORED)); + + assert_commit_message_contains("refs/stash^3", "untracked files on master: "); + + assert_blob_oid("refs/stash^3:what", NULL); + assert_blob_oid("refs/stash^3:how", NULL); + assert_blob_oid("refs/stash^3:who", NULL); + assert_blob_oid("refs/stash^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); + assert_blob_oid("refs/stash^3:just.ignore", "78925fb1236b98b37a35e9723033e627f97aa88b"); +} + +#define MESSAGE "Look Ma! I'm on TV!" +void test_stash_save__can_accept_a_message(void) +{ + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, MESSAGE, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on master: "); + assert_commit_message_contains("refs/stash", "On master: " MESSAGE); +} + +void test_stash_save__cannot_stash_against_an_unborn_branch(void) +{ + git_reference *head; + + cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); + cl_git_pass(git_reference_set_target(head, "refs/heads/unborn")); + + cl_assert_equal_i(GIT_EORPHANEDHEAD, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + git_reference_free(head); +} + +void test_stash_save__cannot_stash_against_a_bare_repository(void) +{ + git_repository *local; + + cl_git_pass(git_repository_init(&local, "sorry-it-is-a-non-bare-only-party", 1)); + + cl_assert_equal_i(GIT_EBAREREPO, + git_stash_save(&stash_tip_oid, local, signature, NULL, GIT_STASH_DEFAULT)); + + git_repository_free(local); +} + +void test_stash_save__can_stash_against_a_detached_head(void) +{ + git_repository_detach_head(repo); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + assert_commit_message_contains("refs/stash^2", "index on (no branch): "); + assert_commit_message_contains("refs/stash", "WIP on (no branch): "); +} + +void test_stash_save__stashing_updates_the_reflog(void) +{ + char *sha; + + assert_object_oid("refs/stash@{0}", NULL, GIT_OBJ_COMMIT); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + sha = git_oid_allocfmt(&stash_tip_oid); + + assert_object_oid("refs/stash@{0}", sha, GIT_OBJ_COMMIT); + assert_object_oid("refs/stash@{1}", NULL, GIT_OBJ_COMMIT); + + git__free(sha); +} + +void test_stash_save__cannot_stash_when_there_are_no_local_change(void) +{ + git_index *index; + git_oid commit_oid, stash_tip_oid; + + cl_git_pass(git_repository_index(&index, repo)); + + /* + * 'what' and 'who' are being committed. + * 'when' remain untracked. + */ + cl_git_pass(git_index_add_from_workdir(index, "what")); + cl_git_pass(git_index_add_from_workdir(index, "who")); + cl_git_pass(git_index_write(index)); + commit_staged_files(&commit_oid, index, signature); + git_index_free(index); + + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + + p_unlink("stash/when"); + cl_assert_equal_i(GIT_ENOTFOUND, + git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); +} + +void test_stash_save__can_stage_normal_then_stage_untracked(void) +{ + /* + * $ git ls-tree stash@{1}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob bc99dc98b3eba0e9157e94769cd4d49cb49de449 what + * 100644 blob a0400d4954659306a976567af43125a0b1aa8595 who + * + * $ git ls-tree stash@{1}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob e6d64adb2c7f3eb8feb493b556cc8070dca379a3 how + * 100644 blob dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{1}^3 + * fatal: Not a valid object name stash@{1}^3 + * + * $ git ls-tree stash@{0}^0 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^1 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^2 + * 100644 blob ac4d88de61733173d9959e4b77c69b9f17a00980 .gitignore + * 100644 blob ac790413e2d7a26c3767e78c57bb28716686eebc how + * 100644 blob ce013625030ba8dba906f756967f9e9ca394464a what + * 100644 blob cc628ccd10742baea8241c5924df992b5c019f71 who + * + * $ git ls-tree stash@{0}^3 + * 100644 blob b6ed15e81e2593d7bb6265eb4a991d29dc3e628b when + */ + + assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_WT_MODIFIED); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_DEFAULT)); + assert_status("what", GIT_STATUS_CURRENT); + assert_status("how", GIT_STATUS_CURRENT); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_STATUS_WT_NEW); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + assert_status("what", GIT_STATUS_CURRENT); + assert_status("how", GIT_STATUS_CURRENT); + assert_status("who", GIT_STATUS_CURRENT); + assert_status("when", GIT_ENOTFOUND); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + + assert_blob_oid("stash@{1}^0:what", "bc99dc98b3eba0e9157e94769cd4d49cb49de449"); /* see you later */ + assert_blob_oid("stash@{1}^0:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^0:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ + assert_blob_oid("stash@{1}^0:when", NULL); + + assert_blob_oid("stash@{1}^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ + assert_blob_oid("stash@{1}^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ + assert_blob_oid("stash@{1}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{1}^2:when", NULL); + + assert_object_oid("stash@{1}^3", NULL, GIT_OBJ_COMMIT); + + assert_blob_oid("stash@{0}^0:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^0:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^0:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^0:when", NULL); + + assert_blob_oid("stash@{0}^2:what", "ce013625030ba8dba906f756967f9e9ca394464a"); /* hello */ + assert_blob_oid("stash@{0}^2:how", "ac790413e2d7a26c3767e78c57bb28716686eebc"); /* small */ + assert_blob_oid("stash@{0}^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ + assert_blob_oid("stash@{0}^2:when", NULL); + + assert_blob_oid("stash@{0}^3:when", "b6ed15e81e2593d7bb6265eb4a991d29dc3e628b"); /* now */ +} + +#define EMPTY_TREE "4b825dc642cb6eb9a060e54bf8d69288fbee4904" + +void test_stash_save__including_untracked_without_any_untracked_file_creates_an_empty_tree(void) +{ + cl_git_pass(p_unlink("stash/when")); + + assert_status("what", GIT_STATUS_WT_MODIFIED | GIT_STATUS_INDEX_MODIFIED); + assert_status("how", GIT_STATUS_INDEX_MODIFIED); + assert_status("who", GIT_STATUS_WT_MODIFIED); + assert_status("when", GIT_ENOTFOUND); + assert_status("just.ignore", GIT_STATUS_IGNORED); + + cl_git_pass(git_stash_save(&stash_tip_oid, repo, signature, NULL, GIT_STASH_INCLUDE_UNTRACKED)); + + assert_object_oid("stash^3^{tree}", EMPTY_TREE, GIT_OBJ_TREE); +} diff --git a/tests-clar/stash/stash_helpers.c b/tests-clar/stash/stash_helpers.c new file mode 100644 index 000000000..86a741853 --- /dev/null +++ b/tests-clar/stash/stash_helpers.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "stash_helpers.h" + +void commit_staged_files( + git_oid *commit_oid, + git_index *index, + git_signature *signature) +{ + git_tree *tree; + git_oid tree_oid; + git_repository *repo; + + repo = git_index_owner(index); + + cl_git_pass(git_index_write_tree(&tree_oid, index)); + + cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); + + cl_git_pass(git_commit_create_v( + commit_oid, + repo, + "HEAD", + signature, + signature, + NULL, + "Initial commit", + tree, + 0)); + + git_tree_free(tree); +} + +void setup_stash(git_repository *repo, git_signature *signature) +{ + git_oid commit_oid; + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("stash/what", "hello\n"); /* ce013625030ba8dba906f756967f9e9ca394464a */ + cl_git_mkfile("stash/how", "small\n"); /* ac790413e2d7a26c3767e78c57bb28716686eebc */ + cl_git_mkfile("stash/who", "world\n"); /* cc628ccd10742baea8241c5924df992b5c019f71 */ + cl_git_mkfile("stash/when", "now\n"); /* b6ed15e81e2593d7bb6265eb4a991d29dc3e628b */ + cl_git_mkfile("stash/just.ignore", "me\n"); /* 78925fb1236b98b37a35e9723033e627f97aa88b */ + + cl_git_mkfile("stash/.gitignore", "*.ignore\n"); + + cl_git_pass(git_index_add_from_workdir(index, "what")); + cl_git_pass(git_index_add_from_workdir(index, "how")); + cl_git_pass(git_index_add_from_workdir(index, "who")); + cl_git_pass(git_index_add_from_workdir(index, ".gitignore")); + cl_git_pass(git_index_write(index)); + + commit_staged_files(&commit_oid, index, signature); + + cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ + cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ + cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */ + + cl_git_pass(git_index_add_from_workdir(index, "what")); + cl_git_pass(git_index_add_from_workdir(index, "how")); + cl_git_pass(git_index_write(index)); + + cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */ + + git_index_free(index); +} diff --git a/tests-clar/stash/stash_helpers.h b/tests-clar/stash/stash_helpers.h new file mode 100644 index 000000000..bb7fec4f5 --- /dev/null +++ b/tests-clar/stash/stash_helpers.h @@ -0,0 +1,8 @@ +void setup_stash( + git_repository *repo, + git_signature *signature); + +void commit_staged_files( + git_oid *commit_oid, + git_index *index, + git_signature *signature);
\ No newline at end of file diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 908d34510..c154179b0 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -71,7 +71,7 @@ static int remove_file_cb(void *data, git_buf *file) return 0; if (git_path_isdir(filename)) - cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(filename, NULL, GIT_RMDIR_REMOVE_FILES)); else cl_git_pass(p_unlink(git_buf_cstr(file))); @@ -314,7 +314,7 @@ void test_status_worktree__issue_592_3(void) repo = cl_git_sandbox_init("issue_592"); cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "c")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(git_status_foreach(repo, cb_status__check_592, "c/a.txt")); @@ -344,7 +344,7 @@ void test_status_worktree__issue_592_5(void) repo = cl_git_sandbox_init("issue_592"); cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(repo), "t")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(p_mkdir(git_buf_cstr(&path), 0777)); cl_git_pass(git_status_foreach(repo, cb_status__check_592, NULL)); @@ -438,7 +438,7 @@ void test_status_worktree__first_commit_in_progress(void) cl_assert(result.status == GIT_STATUS_WT_NEW); cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -486,7 +486,7 @@ static void fill_index_wth_head_entries(git_repository *repo, git_index *index) cl_git_pass(git_commit_lookup(&commit, repo, &oid)); cl_git_pass(git_commit_tree(&tree, commit)); - cl_git_pass(git_index_read_tree(index, tree, NULL)); + cl_git_pass(git_index_read_tree(index, tree)); cl_git_pass(git_index_write(index)); git_tree_free(tree); @@ -570,7 +570,7 @@ void test_status_worktree__bracket_in_filename(void) /* add the file to the index */ cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_BRACKET)); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -648,7 +648,7 @@ void test_status_worktree__space_in_filename(void) /* add the file to the index */ cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, FILE_WITH_SPACE, 0)); + cl_git_pass(git_index_add_from_workdir(index, FILE_WITH_SPACE)); cl_git_pass(git_index_write(index)); memset(&result, 0, sizeof(result)); @@ -816,7 +816,7 @@ void test_status_worktree__new_staged_file_must_handle_crlf(void) cl_git_mkfile("getting_started/testfile.txt", "content\r\n"); // Content with CRLF cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add(index, "testfile.txt", 0)); + cl_git_pass(git_index_add_from_workdir(index, "testfile.txt")); cl_git_pass(git_index_write(index)); cl_git_pass(git_status_file(&status, repo, "testfile.txt")); diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c index 63073ceca..325013466 100644 --- a/tests-clar/submodule/status.c +++ b/tests-clar/submodule/status.c @@ -50,7 +50,7 @@ void test_submodule_status__ignore_none(void) git_buf path = GIT_BUF_INIT; cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); @@ -105,7 +105,7 @@ void test_submodule_status__ignore_none(void) cl_git_pass(git_repository_index(&index, g_repo)); pos = git_index_find(index, "sm_changed_head"); cl_assert(pos >= 0); - cl_git_pass(git_index_remove(index, pos)); + cl_git_pass(git_index_remove(index, "sm_changed_head", 0)); cl_git_pass(git_index_write(index)); git_index_free(index); @@ -135,7 +135,7 @@ void test_submodule_status__ignore_untracked(void) git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_UNTRACKED; cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); @@ -195,7 +195,7 @@ void test_submodule_status__ignore_dirty(void) git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_DIRTY; cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); @@ -255,7 +255,7 @@ void test_submodule_status__ignore_all(void) git_submodule_ignore_t ign = GIT_SUBMODULE_IGNORE_ALL; cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "sm_unchanged")); - cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_git_pass(git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_REMOVE_FILES)); cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); |
