diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | CMakeLists.txt | 11 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | include/git2/blob.h | 42 | ||||
-rw-r--r-- | include/git2/errors.h | 1 | ||||
-rw-r--r-- | include/git2/remote.h | 9 | ||||
-rw-r--r-- | src/blob.c | 71 | ||||
-rw-r--r-- | src/common.h | 1 | ||||
-rw-r--r-- | src/config.c | 1 | ||||
-rw-r--r-- | src/fetch.c | 4 | ||||
-rw-r--r-- | src/fetch.h | 2 | ||||
-rw-r--r-- | src/filebuf.c | 5 | ||||
-rw-r--r-- | src/filebuf.h | 1 | ||||
-rw-r--r-- | src/netops.c | 322 | ||||
-rw-r--r-- | src/netops.h | 13 | ||||
-rw-r--r-- | src/pkt.c | 6 | ||||
-rw-r--r-- | src/remote.c | 10 | ||||
-rw-r--r-- | src/remote.h | 3 | ||||
-rw-r--r-- | src/repository.c | 74 | ||||
-rw-r--r-- | src/transport.c | 2 | ||||
-rw-r--r-- | src/transport.h | 24 | ||||
-rw-r--r-- | src/transports/git.c | 53 | ||||
-rw-r--r-- | src/transports/http.c | 83 | ||||
-rw-r--r-- | src/util.c | 2 | ||||
-rw-r--r-- | src/win32/msvc-compat.h | 1 | ||||
-rw-r--r-- | tests-clar/core/errors.c | 2 | ||||
-rw-r--r-- | tests-clar/diff/iterator.c | 8 | ||||
-rw-r--r-- | tests-clar/diff/workdir.c | 20 | ||||
-rw-r--r-- | tests-clar/network/remotes.c | 2 | ||||
-rw-r--r-- | tests-clar/object/blob/fromchunks.c | 87 | ||||
-rw-r--r-- | tests-clar/repo/init.c | 68 | ||||
-rw-r--r-- | tests-clar/resources/status/这 | 1 | ||||
-rw-r--r-- | tests-clar/status/status_data.h | 16 |
33 files changed, 819 insertions, 129 deletions
diff --git a/.gitignore b/.gitignore index efc1524e6..45d7b1957 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /tests-clar/clar.h /tests-clar/clar_main.c +/tests-clar/clar_main.c.rule /apidocs /trash-*.exe /libgit2.pc diff --git a/CMakeLists.txt b/CMakeLists.txt index 8018ea72d..b09729364 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,6 +89,13 @@ 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 () +FIND_PACKAGE(OpenSSL) +IF (OPENSSL_FOUND) + ADD_DEFINITIONS(-DGIT_SSL) + INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + SET(SSL_LIBRARIES ${OPENSSL_LIBRARIES}) +ENDIF() + IF (THREADSAFE) IF (NOT WIN32) find_package(Threads REQUIRED) @@ -121,7 +128,7 @@ ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") TARGET_LINK_LIBRARIES(git2 socket nsl) ENDIF () -TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT}) +TARGET_LINK_LIBRARIES(git2 ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) SET_TARGET_PROPERTIES(git2 PROPERTIES VERSION ${LIBGIT2_VERSION_STRING}) SET_TARGET_PROPERTIES(git2 PROPERTIES SOVERSION ${LIBGIT2_VERSION_MAJOR}) CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/libgit2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc @ONLY) @@ -157,7 +164,7 @@ IF (BUILD_CLAR) WORKING_DIRECTORY ${CLAR_PATH} ) ADD_EXECUTABLE(libgit2_clar ${SRC} ${CLAR_PATH}/clar_main.c ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX}) - TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT}) + TARGET_LINK_LIBRARIES(libgit2_clar ${CMAKE_THREAD_LIBS_INIT} ${SSL_LIBRARIES}) IF (WIN32) TARGET_LINK_LIBRARIES(libgit2_clar ws2_32) ELSEIF (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") @@ -89,7 +89,7 @@ Here are the bindings to libgit2 that are currently available: * Go * go-git <https://github.com/str1ngs/go-git> * GObject - * libgit2-glib <https://github.com/nacho/libgit2-glib> + * libgit2-glib <http://git.gnome.org/browse/libgit2-glib> * Haskell * hgit2 <https://github.com/norm2782/hgit2> * Lua diff --git a/include/git2/blob.h b/include/git2/blob.h index 551770678..544dc7c41 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -115,6 +115,48 @@ GIT_EXTERN(int) git_blob_create_fromfile(git_oid *oid, git_repository *repo, con */ GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *path); +/** + * Write a loose blob to the Object Database from a + * provider of chunks of data. + * + * Provided the `hintpath` parameter is filled, its value + * will help to determine what git filters should be applied + * to the object before it can be placed to the object database. + * + * + * The implementation of the callback has to respect the + * following rules: + * + * - `content` will have to be filled by the consumer. The maximum number + * of bytes that the buffer can accept per call is defined by the + * `max_length` parameter. Allocation and freeing of the buffer will be taken + * care of by the function. + * + * - The callback is expected to return the number of bytes + * that `content` have been filled with. + * + * - When there is no more data to stream, the callback should + * return 0. This will prevent it from being invoked anymore. + * + * - When an error occurs, the callback should return -1. + * + * + * @param oid Return the id of the written blob + * + * @param repo repository where the blob will be written. + * This repository can be bare or not. + * + * @param hintpath if not NULL, will help selecting the filters + * to apply onto the content of the blob to be created. + * + * @return GIT_SUCCESS or an error code + */ +GIT_EXTERN(int) git_blob_create_fromchunks( + git_oid *oid, + git_repository *repo, + const char *hintpath, + int (*source_cb)(char *content, size_t max_length, void *payload), + void *payload); /** * Write an in-memory buffer to the ODB as a blob diff --git a/include/git2/errors.h b/include/git2/errors.h index fb6670004..ccbc9fcf4 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -88,6 +88,7 @@ typedef enum { GITERR_TAG, GITERR_TREE, GITERR_INDEXER, + GITERR_SSL, } git_error_t; /** diff --git a/include/git2/remote.h b/include/git2/remote.h index 7a032dbce..5c01949d2 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -229,6 +229,15 @@ GIT_EXTERN(int) git_remote_list(git_strarray *remotes_list, git_repository *repo */ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const char *name, const char *url); +/** + * Choose whether to check the server's certificate (applies to HTTPS only) + * + * @param remote the remote to configure + * @param check whether to check the server's certificate (defaults to yes) + */ + +GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); + /** @} */ GIT_END_DECL #endif diff --git a/src/blob.c b/src/blob.c index e25944b91..699adec6b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -148,27 +148,31 @@ static int write_symlink( return error; } -static int blob_create_internal(git_oid *oid, git_repository *repo, const char *path) +static int blob_create_internal(git_oid *oid, git_repository *repo, const char *content_path, const char *hint_path, bool try_load_filters) { int error; struct stat st; git_odb *odb = NULL; git_off_t size; - if ((error = git_path_lstat(path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) + assert(hint_path || !try_load_filters); + + if ((error = git_path_lstat(content_path, &st)) < 0 || (error = git_repository_odb__weakptr(&odb, repo)) < 0) return error; size = st.st_size; if (S_ISLNK(st.st_mode)) { - error = write_symlink(oid, odb, path, (size_t)size); + error = write_symlink(oid, odb, content_path, (size_t)size); } else { git_vector write_filters = GIT_VECTOR_INIT; - int filter_count; + int filter_count = 0; - /* Load the filters for writing this file to the ODB */ - filter_count = git_filters_load( - &write_filters, repo, path, GIT_FILTER_TO_ODB); + if (try_load_filters) { + /* Load the filters for writing this file to the ODB */ + filter_count = git_filters_load( + &write_filters, repo, hint_path, GIT_FILTER_TO_ODB); + } if (filter_count < 0) { /* Negative value means there was a critical error */ @@ -176,10 +180,10 @@ static int blob_create_internal(git_oid *oid, git_repository *repo, const char * } else if (filter_count == 0) { /* No filters need to be applied to the document: we can stream * directly from disk */ - error = write_file_stream(oid, odb, path, size); + error = write_file_stream(oid, odb, content_path, size); } else { /* We need to apply one or more filters */ - error = write_file_filtered(oid, odb, path, &write_filters); + error = write_file_filtered(oid, odb, content_path, &write_filters); } git_filters_free(&write_filters); @@ -216,7 +220,7 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat return -1; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path)); + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); git_buf_free(&full_path); return error; @@ -232,8 +236,53 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat return error; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path)); + error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); git_buf_free(&full_path); return error; } + +#define BUFFER_SIZE 4096 + +int git_blob_create_fromchunks( + git_oid *oid, + git_repository *repo, + const char *hintpath, + int (*source_cb)(char *content, size_t max_length, void *payload), + void *payload) +{ + int error = -1, read_bytes; + char *content = NULL; + git_filebuf file = GIT_FILEBUF_INIT; + + content = git__malloc(BUFFER_SIZE); + GITERR_CHECK_ALLOC(content); + + if (git_filebuf_open(&file, hintpath == NULL ? "streamed" : hintpath, GIT_FILEBUF_TEMPORARY) < 0) + goto cleanup; + + while (1) { + read_bytes = source_cb(content, BUFFER_SIZE, payload); + + assert(read_bytes <= BUFFER_SIZE); + + if (read_bytes <= 0) + break; + + if (git_filebuf_write(&file, content, read_bytes) < 0) + goto cleanup; + } + + if (read_bytes < 0) + goto cleanup; + + if (git_filebuf_flush(&file) < 0) + goto cleanup; + + error = blob_create_internal(oid, repo, file.path_lock, hintpath, hintpath != NULL); + +cleanup: + git_filebuf_cleanup(&file); + git__free(content); + return error; +} diff --git a/src/common.h b/src/common.h index 30757de70..e2a300291 100644 --- a/src/common.h +++ b/src/common.h @@ -65,7 +65,6 @@ void giterr_clear(void); void giterr_set_str(int error_class, const char *string); void giterr_set_regex(const regex_t *regex, int error_code); - #include "util.h" diff --git a/src/config.c b/src/config.c index 618202c34..d18b85c30 100644 --- a/src/config.c +++ b/src/config.c @@ -391,7 +391,6 @@ int git_config_get_string(const char **out, git_config *cfg, const char *name) return ret; } - giterr_set(GITERR_CONFIG, "Config variable '%s' not found", name); return GIT_ENOTFOUND; } diff --git a/src/fetch.c b/src/fetch.c index c92cf4ef5..96b263faa 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -110,7 +110,7 @@ int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_st int git_fetch__download_pack( const char *buffered, size_t buffered_size, - GIT_SOCKET fd, + git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) @@ -120,7 +120,7 @@ int git_fetch__download_pack( gitno_buffer buf; git_indexer_stream *idx; - gitno_buffer_setup(&buf, buff, sizeof(buff), fd); + gitno_buffer_setup(t, &buf, buff, sizeof(buff)); if (memcmp(buffered, "PACK", strlen("PACK"))) { giterr_set(GITERR_NET, "The pack doesn't start with the signature"); diff --git a/src/fetch.h b/src/fetch.h index b3192a563..a7f126520 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -12,7 +12,7 @@ int git_fetch_negotiate(git_remote *remote); int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats); -int git_fetch__download_pack(const char *buffered, size_t buffered_size, GIT_SOCKET fd, +int git_fetch__download_pack(const char *buffered, size_t buffered_size, git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); int git_fetch_setup_walk(git_revwalk **out, git_repository *repo); diff --git a/src/filebuf.c b/src/filebuf.c index 6538aea66..876f8e3e7 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -130,6 +130,11 @@ GIT_INLINE(int) flush_buffer(git_filebuf *file) return result; } +int git_filebuf_flush(git_filebuf *file) +{ + return flush_buffer(file); +} + static int write_normal(git_filebuf *file, void *source, size_t len) { if (len > 0) { diff --git a/src/filebuf.h b/src/filebuf.h index 72563b57a..377883147 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -81,5 +81,6 @@ int git_filebuf_commit(git_filebuf *lock, mode_t mode); int git_filebuf_commit_at(git_filebuf *lock, const char *path, mode_t mode); void git_filebuf_cleanup(git_filebuf *lock); int git_filebuf_hash(git_oid *oid, git_filebuf *file); +int git_filebuf_flush(git_filebuf *file); #endif diff --git a/src/netops.c b/src/netops.c index e16cae8e6..e6f3e5627 100644 --- a/src/netops.c +++ b/src/netops.c @@ -10,6 +10,7 @@ # include <sys/select.h> # include <sys/time.h> # include <netdb.h> +# include <arpa/inet.h> #else # include <winsock2.h> # include <ws2tcpip.h> @@ -18,13 +19,19 @@ # endif #endif +#ifdef GIT_SSL +# include <openssl/ssl.h> +# include <openssl/x509v3.h> +#endif +#include <ctype.h> #include "git2/errors.h" #include "common.h" #include "netops.h" #include "posix.h" #include "buffer.h" +#include "transport.h" #ifdef GIT_WIN32 static void net_set_error(const char *str) @@ -45,25 +52,68 @@ static void net_set_error(const char *str) } #endif -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd) +#ifdef GIT_SSL +static int ssl_set_error(gitno_ssl *ssl, int error) +{ + int err; + err = SSL_get_error(ssl->ssl, error); + giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + return -1; +} +#endif + +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) { memset(buf, 0x0, sizeof(gitno_buffer)); memset(data, 0x0, len); buf->data = data; buf->len = len; buf->offset = 0; - buf->fd = fd; + buf->fd = t->socket; +#ifdef GIT_SSL + if (t->encrypt) + buf->ssl = &t->ssl; +#endif } +#ifdef GIT_SSL +static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +{ + int ret; + + do { + ret = SSL_read(ssl->ssl, data, len); + } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + + if (ret < 0) + return ssl_set_error(ssl, ret); + + return ret; +} +#endif + int gitno_recv(gitno_buffer *buf) { int ret; +#ifdef GIT_SSL + if (buf->ssl != NULL) { + if ((ret = ssl_recv(buf->ssl, buf->data + buf->offset, buf->len - buf->offset)) < 0) + return -1; + } else { + ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } + } +#else ret = p_recv(buf->fd, buf->data + buf->offset, buf->len - buf->offset, 0); if (ret < 0) { net_set_error("Error receiving socket data"); return -1; } +#endif buf->offset += ret; return ret; @@ -92,7 +142,239 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons) buf->offset -= cons; } -int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) +int gitno_ssl_teardown(git_transport *t) +{ +#ifdef GIT_SSL + int ret; +#endif + + if (!t->encrypt) + return 0; + +#ifdef GIT_SSL + + do { + ret = SSL_shutdown(t->ssl.ssl); + } while (ret == 0); + if (ret < 0) + return ssl_set_error(&t->ssl, ret); + + SSL_free(t->ssl.ssl); + SSL_CTX_free(t->ssl.ctx); +#endif + return 0; +} + + +#ifdef GIT_SSL +/* Match host names according to RFC 2818 rules */ +static int match_host(const char *pattern, const char *host) +{ + for (;;) { + char c = tolower(*pattern++); + + if (c == '\0') + return *host ? -1 : 0; + + if (c == '*') { + c = *pattern; + /* '*' at the end matches everything left */ + if (c == '\0') + return 0; + + /* + * We've found a pattern, so move towards the next matching + * char. The '.' is handled specially because wildcards aren't + * allowed to cross subdomains. + */ + + while(*host) { + char h = tolower(*host); + if (c == h) + return match_host(pattern, host++); + if (h == '.') + return match_host(pattern, host); + host++; + } + return -1; + } + + if (c != tolower(*host++)) + return -1; + } + + return -1; +} + +static int check_host_name(const char *name, const char *host) +{ + if (!strcasecmp(name, host)) + return 0; + + if (match_host(name, host) < 0) + return -1; + + return 0; +} + +static int verify_server_cert(git_transport *t, const char *host) +{ + X509 *cert; + X509_NAME *peer_name; + ASN1_STRING *str; + unsigned char *peer_cn = NULL; + int matched = -1, type = GEN_DNS; + GENERAL_NAMES *alts; + struct in6_addr addr6; + struct in_addr addr4; + void *addr; + int i = -1,j; + + + /* Try to parse the host as an IP address to see if it is */ + if (inet_pton(AF_INET, host, &addr4)) { + type = GEN_IPADD; + addr = &addr4; + } else { + if(inet_pton(AF_INET6, host, &addr6)) { + type = GEN_IPADD; + addr = &addr6; + } + } + + + cert = SSL_get_peer_certificate(t->ssl.ssl); + + /* Check the alternative names */ + alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL); + if (alts) { + int num; + + num = sk_GENERAL_NAME_num(alts); + for (i = 0; i < num && matched != 1; i++) { + const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i); + const char *name = (char *) ASN1_STRING_data(gn->d.ia5); + size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5); + + /* Skip any names of a type we're not looking for */ + if (gn->type != type) + continue; + + if (type == GEN_DNS) { + /* If it contains embedded NULs, don't even try */ + if (memchr(name, '\0', namelen)) + continue; + + if (check_host_name(name, host) < 0) + matched = 0; + else + matched = 1; + } else if (type == GEN_IPADD) { + /* Here name isn't so much a name but a binary representation of the IP */ + matched = !!memcmp(name, addr, namelen); + } + } + } + GENERAL_NAMES_free(alts); + + if (matched == 0) + goto on_error; + + if (matched == 1) + return 0; + + /* If no alternative names are available, check the common name */ + peer_name = X509_get_subject_name(cert); + if (peer_name == NULL) + goto on_error; + + if (peer_name) { + /* Get the index of the last CN entry */ + while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0) + i = j; + } + + if (i < 0) + goto on_error; + + str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i)); + if (str == NULL) + goto on_error; + + /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */ + if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) { + int size = ASN1_STRING_length(str); + + if (size > 0) { + peer_cn = OPENSSL_malloc(size + 1); + GITERR_CHECK_ALLOC(peer_cn); + memcpy(peer_cn, ASN1_STRING_data(str), size); + peer_cn[size] = '\0'; + } + } else { + int size = ASN1_STRING_to_UTF8(&peer_cn, str); + GITERR_CHECK_ALLOC(peer_cn); + if (memchr(peer_cn, '\0', size)) + goto cert_fail; + } + + if (check_host_name((char *)peer_cn, host) < 0) + goto cert_fail; + + OPENSSL_free(peer_cn); + + return 0; + +on_error: + OPENSSL_free(peer_cn); + return ssl_set_error(&t->ssl, 0); + +cert_fail: + OPENSSL_free(peer_cn); + giterr_set(GITERR_SSL, "Certificate host name check failed"); + return -1; +} + +static int ssl_setup(git_transport *t, const char *host) +{ + int ret; + + SSL_library_init(); + SSL_load_error_strings(); + t->ssl.ctx = SSL_CTX_new(SSLv23_method()); + if (t->ssl.ctx == NULL) + return ssl_set_error(&t->ssl, 0); + + SSL_CTX_set_mode(t->ssl.ctx, SSL_MODE_AUTO_RETRY); + SSL_CTX_set_verify(t->ssl.ctx, SSL_VERIFY_PEER, NULL); + if (!SSL_CTX_set_default_verify_paths(t->ssl.ctx)) + return ssl_set_error(&t->ssl, 0); + + t->ssl.ssl = SSL_new(t->ssl.ctx); + if (t->ssl.ssl == NULL) + return ssl_set_error(&t->ssl, 0); + + if((ret = SSL_set_fd(t->ssl.ssl, t->socket)) == 0) + return ssl_set_error(&t->ssl, ret); + + if ((ret = SSL_connect(t->ssl.ssl)) <= 0) + return ssl_set_error(&t->ssl, ret); + + if (t->check_cert && verify_server_cert(t, host) < 0) + return -1; + + return 0; +} +#else +static int ssl_setup(git_transport *t, const char *host) +{ + GIT_UNUSED(t); + GIT_UNUSED(host); + return 0; +} +#endif + +int gitno_connect(git_transport *t, const char *host, const char *port) { struct addrinfo *info = NULL, *p; struct addrinfo hints; @@ -129,20 +411,46 @@ int gitno_connect(GIT_SOCKET *sock, const char *host, const char *port) return -1; } + t->socket = s; freeaddrinfo(info); - *sock = s; + + if (t->encrypt && ssl_setup(t, host) < 0) + return -1; + return 0; } -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags) +#ifdef GIT_SSL +static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) { int ret; size_t off = 0; while (off < len) { - errno = 0; + ret = SSL_write(ssl->ssl, msg + off, len - off); + if (ret <= 0) + return ssl_set_error(ssl, ret); - ret = p_send(s, msg + off, len - off, flags); + off += ret; + } + + return off; +} +#endif + +int gitno_send(git_transport *t, const char *msg, size_t len, int flags) +{ + int ret; + size_t off = 0; + +#ifdef GIT_SSL + if (t->encrypt) + return send_ssl(&t->ssl, msg, len); +#endif + + while (off < len) { + errno = 0; + ret = p_send(t->socket, msg + off, len - off, flags); if (ret < 0) { net_set_error("Error sending data"); return -1; diff --git a/src/netops.h b/src/netops.h index 9d13f3891..4976f87f8 100644 --- a/src/netops.h +++ b/src/netops.h @@ -8,22 +8,29 @@ #define INCLUDE_netops_h__ #include "posix.h" +#include "transport.h" +#include "common.h" typedef struct gitno_buffer { char *data; size_t len; size_t offset; GIT_SOCKET fd; +#ifdef GIT_SSL + struct gitno_ssl *ssl; +#endif } gitno_buffer; -void gitno_buffer_setup(gitno_buffer *buf, char *data, unsigned int len, GIT_SOCKET fd); +void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len); int gitno_recv(gitno_buffer *buf); + void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -int gitno_connect(GIT_SOCKET *s, const char *host, const char *port); -int gitno_send(GIT_SOCKET s, const char *msg, size_t len, int flags); +int gitno_connect(git_transport *t, const char *host, const char *port); +int gitno_send(git_transport *t, const char *msg, size_t len, int flags); int gitno_close(GIT_SOCKET s); +int gitno_ssl_teardown(git_transport *t); int gitno_send_chunk_size(int s, size_t len); int gitno_select_in(gitno_buffer *buf, long int sec, long int usec); @@ -281,12 +281,6 @@ int git_pkt_buffer_flush(git_buf *buf) return git_buf_put(buf, pkt_flush_str, strlen(pkt_flush_str)); } -int git_pkt_send_flush(GIT_SOCKET s) -{ - - return gitno_send(s, pkt_flush_str, strlen(pkt_flush_str), 0); -} - static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) { char capstr[20]; diff --git a/src/remote.c b/src/remote.c index 5993ad02b..8d6076107 100644 --- a/src/remote.c +++ b/src/remote.c @@ -66,6 +66,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; if (git_vector_init(&remote->refs, 32, NULL) < 0) return -1; @@ -108,6 +109,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) GITERR_CHECK_ALLOC(remote); memset(remote, 0x0, sizeof(git_remote)); + remote->check_cert = 1; remote->name = git__strdup(name); GITERR_CHECK_ALLOC(remote->name); @@ -291,6 +293,7 @@ int git_remote_connect(git_remote *remote, int direction) if (git_transport_new(&t, remote->url) < 0) return -1; + t->check_cert = remote->check_cert; if (t->connect(t, direction) < 0) { goto on_error; } @@ -512,3 +515,10 @@ on_error: git_remote_free(*out); return -1; } + +void git_remote_check_cert(git_remote *remote, int check) +{ + assert(remote); + + remote->check_cert = check; +} diff --git a/src/remote.h b/src/remote.h index 5a1625d05..0949ad434 100644 --- a/src/remote.h +++ b/src/remote.h @@ -19,7 +19,8 @@ struct git_remote { struct git_refspec push; git_transport *transport; git_repository *repo; - unsigned int need_pack:1; + unsigned int need_pack:1, + check_cert; }; #endif diff --git a/src/repository.c b/src/repository.c index 5120356bf..718170839 100644 --- a/src/repository.c +++ b/src/repository.c @@ -655,7 +655,46 @@ static int repo_init_createhead(const char *git_dir) return 0; } -static int repo_init_config(const char *git_dir, int is_bare) +static bool is_chmod_supported(const char *file_path) +{ + struct stat st1, st2; + static int _is_supported = -1; + + if (_is_supported > -1) + return _is_supported; + + if (p_stat(file_path, &st1) < 0) + return false; + + if (p_chmod(file_path, st1.st_mode ^ S_IXUSR) < 0) + return false; + + if (p_stat(file_path, &st2) < 0) + return false; + + _is_supported = (st1.st_mode != st2.st_mode); + return _is_supported; +} + +static bool is_filesystem_case_insensitive(const char *gitdir_path) +{ + git_buf path = GIT_BUF_INIT; + static int _is_insensitive = -1; + + if (_is_insensitive > -1) + return _is_insensitive; + + if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0) + goto cleanup; + + _is_insensitive = git_path_exists(git_buf_cstr(&path)); + +cleanup: + git_buf_free(&path); + return _is_insensitive; +} + +static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) { git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; @@ -670,13 +709,17 @@ static int repo_init_config(const char *git_dir, int is_bare) if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) return -1; - if (git_config_open_ondisk(&config, cfg_path.ptr) < 0) { + if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { git_buf_free(&cfg_path); return -1; } SET_REPO_CONFIG(bool, "core.bare", is_bare); SET_REPO_CONFIG(int32, "core.repositoryformatversion", GIT_REPO_VERSION); + SET_REPO_CONFIG(bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); + + if (!is_reinit && is_filesystem_case_insensitive(git_dir)) + SET_REPO_CONFIG(bool, "core.ignorecase", true); /* TODO: what other defaults? */ git_buf_free(&cfg_path); @@ -790,30 +833,35 @@ static int repo_init_structure(const char *git_dir, int is_bare) int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) { git_buf repository_path = GIT_BUF_INIT; + bool is_reinit; + int result = -1; assert(repo_out && path); if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) - return -1; + goto cleanup; - if (git_path_isdir(repository_path.ptr) == true) { - if (valid_repository_path(&repository_path) == true) { - int res = repo_init_reinit(repo_out, repository_path.ptr, is_bare); - git_buf_free(&repository_path); - return res; - } + is_reinit = git_path_isdir(repository_path.ptr) && valid_repository_path(&repository_path); + + if (is_reinit) { + if (repo_init_reinit(repo_out, repository_path.ptr, is_bare) < 0) + goto cleanup; + + result = repo_init_config(repository_path.ptr, is_bare, is_reinit); } if (repo_init_structure(repository_path.ptr, is_bare) < 0 || - repo_init_config(repository_path.ptr, is_bare) < 0 || + repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0 || repo_init_createhead(repository_path.ptr) < 0 || git_repository_open(repo_out, repository_path.ptr) < 0) { - git_buf_free(&repository_path); - return -1; + goto cleanup; } + result = 0; + +cleanup: git_buf_free(&repository_path); - return 0; + return result; } int git_repository_head_detached(git_repository *repo) diff --git a/src/transport.c b/src/transport.c index 5b2cd7ea4..fb2b94946 100644 --- a/src/transport.c +++ b/src/transport.c @@ -17,7 +17,7 @@ static struct { } transports[] = { {"git://", git_transport_git}, {"http://", git_transport_http}, - {"https://", git_transport_dummy}, + {"https://", git_transport_https}, {"file://", git_transport_local}, {"git+ssh://", git_transport_dummy}, {"ssh+git://", git_transport_dummy}, diff --git a/src/transport.h b/src/transport.h index 125df2745..68b92f7a6 100644 --- a/src/transport.h +++ b/src/transport.h @@ -10,6 +10,13 @@ #include "git2/net.h" #include "git2/indexer.h" #include "vector.h" +#include "posix.h" +#include "common.h" +#ifdef GIT_SSL +# include <openssl/ssl.h> +# include <openssl/err.h> +#endif + #define GIT_CAP_OFS_DELTA "ofs-delta" @@ -18,6 +25,14 @@ typedef struct git_transport_caps { ofs_delta:1; } git_transport_caps; +#ifdef GIT_SSL +typedef struct gitno_ssl { + SSL_CTX *ctx; + SSL *ssl; +} gitno_ssl; +#endif + + /* * A day in the life of a network operation * ======================================== @@ -53,7 +68,13 @@ struct git_transport { * Whether we want to push or fetch */ int direction : 1, /* 0 fetch, 1 push */ - connected : 1; + connected : 1, + check_cert: 1, + encrypt : 1; +#ifdef GIT_SSL + struct gitno_ssl ssl; +#endif + GIT_SOCKET socket; /** * Connect and store the remote heads */ @@ -94,6 +115,7 @@ 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); /** diff --git a/src/transports/git.c b/src/transports/git.c index 844b350be..45f571f20 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -25,7 +25,6 @@ typedef struct { git_transport parent; git_protocol proto; - GIT_SOCKET socket; git_vector refs; git_remote_head **heads; git_transport_caps caps; @@ -77,7 +76,7 @@ static int gen_proto(git_buf *request, const char *cmd, const char *url) return 0; } -static int send_request(GIT_SOCKET s, const char *cmd, const char *url) +static int send_request(git_transport *t, const char *cmd, const char *url) { int error; git_buf request = GIT_BUF_INIT; @@ -86,7 +85,7 @@ static int send_request(GIT_SOCKET s, const char *cmd, const char *url) if (error < 0) goto cleanup; - error = gitno_send(s, request.ptr, request.size, 0); + error = gitno_send(t, request.ptr, request.size, 0); cleanup: git_buf_free(&request); @@ -102,9 +101,6 @@ static int do_connect(transport_git *t, const char *url) { char *host, *port; const char prefix[] = "git://"; - int error; - - t->socket = INVALID_SOCKET; if (!git__prefixcmp(url, prefix)) url += strlen(prefix); @@ -112,24 +108,22 @@ static int do_connect(transport_git *t, const char *url) if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0) return -1; - if ((error = gitno_connect(&t->socket, host, port)) == 0) { - error = send_request(t->socket, NULL, url); - } + if (gitno_connect((git_transport *)t, host, port) < 0) + goto on_error; + + if (send_request((git_transport *)t, NULL, url) < 0) + goto on_error; git__free(host); git__free(port); - if (error < 0 && t->socket != INVALID_SOCKET) { - gitno_close(t->socket); - t->socket = INVALID_SOCKET; - } - - if (t->socket == INVALID_SOCKET) { - giterr_set(GITERR_NET, "Failed to connect to the host"); - return -1; - } - return 0; + +on_error: + git__free(host); + git__free(port); + gitno_close(t->parent.socket); + return -1; } /* @@ -215,7 +209,7 @@ static int git_connect(git_transport *transport, int direction) if (do_connect(t, transport->url) < 0) goto cleanup; - gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket); + gitno_buffer_setup(transport, &t->buf, t->buff, sizeof(t->buff)); t->parent.connected = 1; if (store_refs(t) < 0) @@ -308,7 +302,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c if (git_fetch_setup_walk(&walk, repo) < 0) goto on_error; - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; git_buf_clear(&data); @@ -328,7 +322,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c if (git_buf_oom(&data)) goto on_error; - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; pkt_type = recv_pkt(buf); @@ -351,7 +345,7 @@ static int git_negotiate_fetch(git_transport *transport, git_repository *repo, c git_buf_clear(&data); git_pkt_buffer_flush(&data); git_pkt_buffer_done(&data); - if (gitno_send(t->socket, data.ptr, data.size, 0) < 0) + if (gitno_send(transport, data.ptr, data.size, 0) < 0) goto on_error; git_buf_free(&data); @@ -392,7 +386,7 @@ static int git_download_pack(git_transport *transport, git_repository *repo, git if (pkt->type == GIT_PKT_PACK) { git__free(pkt); - return git_fetch__download_pack(buf->data, buf->offset, t->socket, repo, bytes, stats); + return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats); } /* For now we don't care about anything */ @@ -406,18 +400,21 @@ static int git_download_pack(git_transport *transport, git_repository *repo, git return read_bytes; } -static int git_close(git_transport *transport) +static int git_close(git_transport *t) { - transport_git *t = (transport_git*) transport; + git_buf buf = GIT_BUF_INIT; + if (git_pkt_buffer_flush(&buf) < 0) + return -1; /* Can't do anything if there's an error, so don't bother checking */ - git_pkt_send_flush(t->socket); + gitno_send(t, buf.ptr, buf.size, 0); + if (gitno_close(t->socket) < 0) { giterr_set(GITERR_NET, "Failed to close socket"); return -1; } - t->parent.connected = 0; + t->connected = 0; #ifdef GIT_WIN32 WSACleanup(); diff --git a/src/transports/http.c b/src/transports/http.c index 9ea21a61d..4139a2fa6 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -32,7 +32,6 @@ typedef struct { git_protocol proto; git_vector refs; git_vector common; - GIT_SOCKET socket; git_buf buf; git_remote_head **heads; int error; @@ -43,6 +42,7 @@ typedef struct { enum last_cb last_cb; http_parser parser; char *content_type; + char *path; char *host; char *port; char *service; @@ -52,12 +52,9 @@ typedef struct { #endif } transport_http; -static int gen_request(git_buf *buf, const char *url, const char *host, const char *op, +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) { - const char *path = url; - - path = strchr(path, '/'); if (path == NULL) /* Is 'git fetch http://host.com/' valid? */ path = "/"; @@ -85,15 +82,12 @@ static int gen_request(git_buf *buf, const char *url, const char *host, const ch static int do_connect(transport_http *t, const char *host, const char *port) { - GIT_SOCKET s; - if (t->parent.connected && http_should_keep_alive(&t->parser)) return 0; - if (gitno_connect(&s, host, port) < 0) + if (gitno_connect((git_transport *) t, host, port) < 0) return -1; - t->socket = s; t->parent.connected = 1; return 0; @@ -231,7 +225,7 @@ static int store_refs(transport_http *t) settings.on_body = on_body_store_refs; settings.on_message_complete = on_message_complete; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); while(1) { size_t parsed; @@ -267,7 +261,8 @@ static int http_connect(git_transport *transport, int direction) int ret; git_buf request = GIT_BUF_INIT; const char *service = "upload-pack"; - const char *url = t->parent.url, *prefix = "http://"; + const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; + const char *default_port; if (direction == GIT_DIR_PUSH) { giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); @@ -278,10 +273,19 @@ static int http_connect(git_transport *transport, int direction) if (git_vector_init(&t->refs, 16, NULL) < 0) return -1; - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + if (!git__prefixcmp(url, prefix_http)) { + url = t->parent.url + strlen(prefix_http); + default_port = "80"; + } + + if (!git__prefixcmp(url, prefix_https)) { + url += strlen(prefix_https); + default_port = "443"; + } + + t->path = strchr(url, '/'); - if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, "80")) < 0) + if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0) goto cleanup; t->service = git__strdup(service); @@ -291,12 +295,13 @@ static int http_connect(git_transport *transport, int direction) goto cleanup; /* Generate and send the HTTP request */ - if ((ret = gen_request(&request, url, t->host, "GET", service, 0, 1)) < 0) { + if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { giterr_set(GITERR_NET, "Failed to generate request"); goto cleanup; } - if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) + + if (gitno_send(transport, request.ptr, request.size, 0) < 0) goto cleanup; ret = store_refs(t); @@ -403,7 +408,7 @@ static int parse_response(transport_http *t) settings.on_body = on_body_parse_response; settings.on_message_complete = on_message_complete; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); while(1) { size_t parsed; @@ -437,13 +442,9 @@ static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_oid oid; git_pkt_ack *pkt; git_vector *common = &t->common; - const char *prefix = "http://", *url = t->parent.url; git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT; - gitno_buffer_setup(&buf, buff, sizeof(buff), t->socket); - /* TODO: Store url in the transport */ - if (!git__prefixcmp(url, prefix)) - url += strlen(prefix); + gitno_buffer_setup(transport, &buf, buff, sizeof(buff)); if (git_vector_init(common, 16, NULL) < 0) return -1; @@ -474,13 +475,13 @@ static int http_negotiate_fetch(git_transport *transport, git_repository *repo, git_pkt_buffer_done(&data); - if ((ret = gen_request(&request, url, t->host, "POST", "upload-pack", data.size, 0)) < 0) + if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", data.size, 0)) < 0) goto cleanup; - if ((ret = gitno_send(t->socket, request.ptr, request.size, 0)) < 0) + if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) goto cleanup; - if ((ret = gitno_send(t->socket, data.ptr, data.size, 0)) < 0) + if ((ret = gitno_send(transport, data.ptr, data.size, 0)) < 0) goto cleanup; git_buf_clear(&request); @@ -547,7 +548,7 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi git_indexer_stream *idx = NULL; download_pack_cbdata data; - gitno_buffer_setup(&buf, buffer, sizeof(buffer), t->socket); + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { giterr_set(GITERR_NET, "The pack doesn't start with a pack signature"); @@ -557,7 +558,6 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi if (git_indexer_stream_new(&idx, git_repository_path(repo)) < 0) return -1; - /* * This is part of the previous response, so we don't want to * re-init the parser, just set these two callbacks. @@ -576,6 +576,8 @@ static int http_download_pack(git_transport *transport, git_repository *repo, gi if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) goto on_error; + gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); + do { size_t parsed; @@ -603,14 +605,15 @@ on_error: static int http_close(git_transport *transport) { - transport_http *t = (transport_http *) transport; + if (gitno_ssl_teardown(transport) < 0) + return -1; - if (gitno_close(t->socket) < 0) { + if (gitno_close(transport->socket) < 0) { giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; } - t->parent.connected = 0; + transport->connected = 0; return 0; } @@ -682,3 +685,23 @@ int git_transport_http(git_transport **out) *out = (git_transport *) t; return 0; } + +int git_transport_https(git_transport **out) +{ +#ifdef GIT_SSL + transport_http *t; + if (git_transport_http((git_transport **)&t) < 0) + return -1; + + t->parent.encrypt = 1; + t->parent.check_cert = 1; + *out = (git_transport *) t; + + return 0; +#else + GIT_UNUSED(out); + + giterr_set(GITERR_NET, "HTTPS support not available"); + return -1; +#endif +} diff --git a/src/util.c b/src/util.c index ce770203a..3093cd767 100644 --- a/src/util.c +++ b/src/util.c @@ -179,7 +179,7 @@ void git__strtolower(char *str) int git__prefixcmp(const char *str, const char *prefix) { for (;;) { - char p = *(prefix++), s; + unsigned char p = *(prefix++), s; if (!p) return 0; if ((s = *(str++)) != p) diff --git a/src/win32/msvc-compat.h b/src/win32/msvc-compat.h index 3ef09c85b..ccc091cd0 100644 --- a/src/win32/msvc-compat.h +++ b/src/win32/msvc-compat.h @@ -21,6 +21,7 @@ /* stat: file mode type testing macros */ # define _S_IFLNK 0120000 # define S_IFLNK _S_IFLNK +# define S_IXUSR 00100 # define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR) # define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG) diff --git a/tests-clar/core/errors.c b/tests-clar/core/errors.c index 0be3e7aca..10c0cdd3f 100644 --- a/tests-clar/core/errors.c +++ b/tests-clar/core/errors.c @@ -31,7 +31,7 @@ void test_core_errors__new_school(void) do { struct stat st; memset(&st, 0, sizeof(st)); - assert(p_lstat("this_file_does_not_exist", &st) < 0); + cl_assert(p_lstat("this_file_does_not_exist", &st) < 0); GIT_UNUSED(st); } while (false); giterr_set(GITERR_OS, "stat failed"); /* internal fn */ diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index be29bea66..eee84810a 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -474,13 +474,14 @@ static const char *status_paths[] = { "subdir/current_file", "subdir/modified_file", "subdir/new_file", + "\xe8\xbf\x99", NULL }; void test_diff_iterator__workdir_1(void) { workdir_iterator_test( - "status", NULL, NULL, 12, 1, status_paths, "ignored_file"); + "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); } static const char *status_paths_range_0[] = { @@ -527,13 +528,14 @@ static const char *status_paths_range_4[] = { "subdir/current_file", "subdir/modified_file", "subdir/new_file", + "\xe8\xbf\x99", NULL }; void test_diff_iterator__workdir_1_ranged_4(void) { workdir_iterator_test( - "status", "subdir/", NULL, 3, 0, status_paths_range_4, NULL); + "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); } static const char *status_paths_range_5[] = { @@ -551,7 +553,7 @@ void test_diff_iterator__workdir_1_ranged_5(void) void test_diff_iterator__workdir_1_ranged_empty_0(void) { workdir_iterator_test( - "status", "z_does_not_exist", NULL, + "status", "\xff_does_not_exist", NULL, 0, 0, NULL, NULL); } diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 1ea1af86a..42152f1ad 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -37,12 +37,12 @@ void test_diff_workdir__to_index(void) * - git diff * - mv .git .gitted */ - cl_assert_equal_i(12, exp.files); + 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(3, exp.file_untracked); + cl_assert_equal_i(4, exp.file_untracked); cl_assert_equal_i(8, exp.hunks); @@ -87,12 +87,12 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 13); + cl_assert(exp.files == 14); cl_assert(exp.file_adds == 0); cl_assert(exp.file_dels == 4); cl_assert(exp.file_mods == 4); cl_assert(exp.file_ignored == 1); - cl_assert(exp.file_untracked == 4); + cl_assert(exp.file_untracked == 5); /* Since there is no git diff equivalent, let's just assume that the * text diffs produced by git_diff_foreach are accurate here. We will @@ -115,12 +115,12 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 14); + cl_assert(exp.files == 15); cl_assert(exp.file_adds == 2); cl_assert(exp.file_dels == 5); cl_assert(exp.file_mods == 4); cl_assert(exp.file_ignored == 1); - cl_assert(exp.file_untracked == 2); + cl_assert(exp.file_untracked == 3); cl_assert(exp.hunks == 11); @@ -144,12 +144,12 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 15); + cl_assert(exp.files == 16); cl_assert(exp.file_adds == 5); cl_assert(exp.file_dels == 4); cl_assert(exp.file_mods == 3); cl_assert(exp.file_ignored == 1); - cl_assert(exp.file_untracked == 2); + cl_assert(exp.file_untracked == 3); cl_assert(exp.hunks == 12); @@ -182,12 +182,12 @@ void test_diff_workdir__to_index_with_pathspec(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(12, exp.files); + 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(3, exp.file_untracked); + cl_assert_equal_i(4, exp.file_untracked); git_diff_list_free(diff); diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index 3f631835c..eb7947dfb 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -167,7 +167,7 @@ void test_network_remotes__loading_a_missing_remote_returns_ENOTFOUND(void) * [...] * [remote "addtest"] * url = http://github.com/libgit2/libgit2 - * fetch = +refs/heads/*:refs/remotes/addtest/* + * fetch = +refs/heads/\*:refs/remotes/addtest/\* */ void test_network_remotes__add(void) { diff --git a/tests-clar/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c new file mode 100644 index 000000000..228e969b6 --- /dev/null +++ b/tests-clar/object/blob/fromchunks.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static char textual_content[] = "libgit2\n\r\n\0"; + +void test_object_blob_fromchunks__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_blob_fromchunks__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int text_chunked_source_cb(char *content, size_t max_length, void *payload) +{ + int *count; + + GIT_UNUSED(max_length); + + count = (int *)payload; + (*count)--; + + if (*count == 0) + return 0; + + strcpy(content, textual_content); + return strlen(textual_content); +} + +void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void) +{ + git_oid expected_oid, oid; + git_object *blob; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_fail(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + + cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); + + cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); + git_object_free(blob); +} + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void write_attributes(git_repository *repo) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); + cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); + + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); + cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); + + git_buf_free(&buf); +} + +static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) +{ + git_oid expected_oid, oid; + int howmany = 7; + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + + cl_git_pass(git_blob_create_fromchunks(&oid, repo, fake_name, text_chunked_source_cb, &howmany)); + cl_assert(git_oid_cmp(&expected_oid, &oid) == 0); +} + +void test_object_blob_fromchunks__creating_a_blob_from_chunks_honors_the_attributes_directives(void) +{ + write_attributes(repo); + + assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); +} diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 7f16b5b7c..af54b2266 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -165,3 +165,71 @@ void test_repo_init__additional_templates(void) git_buf_free(&path); } + +static void assert_config_entry_on_init(const char *config_key, int expected_value) +{ + git_config *config; + int current_value; + + cl_set_cleanup(&cleanup_repository, "config_entry"); + + cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", 1)); + git_repository_config(&config, _repo); + + if (expected_value >= 0) { + cl_git_pass(git_config_get_bool(¤t_value, config, config_key)); + + cl_assert_equal_i(expected_value, current_value); + } else { + int error = git_config_get_bool(¤t_value, config, config_key); + + cl_assert_equal_i(expected_value, error); + } + + git_config_free(config); +} + +void test_repo_init__detect_filemode(void) +{ +#ifdef GIT_WIN32 + assert_config_entry_on_init("core.filemode", false); +#else + assert_config_entry_on_init("core.filemode", true); +#endif +} + +#define CASE_INSENSITIVE_FILESYSTEM (defined GIT_WIN32 || defined __APPLE__) + +void test_repo_init__detect_ignorecase(void) +{ +#if CASE_INSENSITIVE_FILESYSTEM + assert_config_entry_on_init("core.ignorecase", true); +#else + assert_config_entry_on_init("core.ignorecase", GIT_ENOTFOUND); +#endif +} + +void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) +{ + git_config *config; + int current_value; + + /* Init a new repo */ + test_repo_init__detect_ignorecase(); + + /* Change the "core.ignorecase" config value to something unlikely */ + git_repository_config(&config, _repo); + git_config_set_int32(config, "core.ignorecase", 42); + git_config_free(config); + git_repository_free(_repo); + + /* Reinit the repository */ + cl_git_pass(git_repository_init(&_repo, "config_entry/test.git", 1)); + git_repository_config(&config, _repo); + + /* Ensure the "core.ignorecase" config value hasn't been updated */ + cl_git_pass(git_config_get_int32(¤t_value, config, "core.ignorecase")); + cl_assert_equal_i(42, current_value); + + git_config_free(config); +} diff --git a/tests-clar/resources/status/这 b/tests-clar/resources/status/这 new file mode 100644 index 000000000..f0ff9a197 --- /dev/null +++ b/tests-clar/resources/status/这 @@ -0,0 +1 @@ +This diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index f109717e8..043b83009 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -19,6 +19,8 @@ static const char *entry_paths0[] = { "subdir/deleted_file", "subdir/modified_file", "subdir/new_file", + + "\xe8\xbf\x99", }; static const unsigned int entry_statuses0[] = { @@ -38,9 +40,11 @@ static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_DELETED, GIT_STATUS_WT_MODIFIED, GIT_STATUS_WT_NEW, + + GIT_STATUS_WT_NEW, }; -static const size_t entry_count0 = 15; +static const size_t entry_count0 = 16; /* entries for a copy of tests/resources/status with all content * deleted from the working directory @@ -108,6 +112,7 @@ static const char *entry_paths3[] = { "subdir/current_file", "subdir/deleted_file", "subdir/modified_file", + "\xe8\xbf\x99", }; static const unsigned int entry_statuses3[] = { @@ -132,9 +137,10 @@ static const unsigned int entry_statuses3[] = { GIT_STATUS_WT_DELETED, GIT_STATUS_WT_DELETED, GIT_STATUS_WT_DELETED, + GIT_STATUS_WT_NEW, }; -static const size_t entry_count3 = 21; +static const size_t entry_count3 = 22; /* entries for a copy of tests/resources/status with some mods @@ -163,7 +169,8 @@ static const char *entry_paths4[] = { "subdir/deleted_file", "subdir/modified_file", "zzz_new_dir/new_file", - "zzz_new_file" + "zzz_new_file", + "\xe8\xbf\x99", }; static const unsigned int entry_statuses4[] = { @@ -189,6 +196,7 @@ static const unsigned int entry_statuses4[] = { GIT_STATUS_WT_DELETED, GIT_STATUS_WT_NEW, GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, }; -static const size_t entry_count4 = 22; +static const size_t entry_count4 = 23; |