diff --git a/.gitignore b/.gitignore index 45d7b195781..7fa7d547c75 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ CMake* *.cmake .DS_Store *~ +tags diff --git a/.travis.yml b/.travis.yml index 2713651a8e7..54da48a40f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,17 +2,25 @@ # see travis-ci.org for details # As CMake is not officially supported we use erlang VMs -language: erlang +language: c + +compiler: + - gcc + - clang # Settings to try env: - OPTIONS="-DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release" - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=ON" - - CC=i586-mingw32msvc-gcc OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" - + +matrix: + include: + - compiler: i586-mingw32msvc-gcc + env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON" + # Make sure CMake is installed install: - - sudo apt-get install cmake + - sudo apt-get install cmake valgrind # Run the Build script script: @@ -24,6 +32,7 @@ script: # Run Tests after_script: - ctest -V . + - if [ -f ./libgit2_clar ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=../libgit2_clar.supp ./libgit2_clar; else echo "Skipping valgrind"; fi # Only watch the development branch branches: @@ -32,6 +41,11 @@ branches: # Notify development list when needed notifications: + irc: + channels: + - irc.freenode.net#libgit2 + on_success: change + on_failure: always recipients: - vicent@github.com email: diff --git a/CMakeLists.txt b/CMakeLists.txt index a8e646d061a..7a7a943e582 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,12 @@ ENDIF() # Find required dependencies INCLUDE_DIRECTORIES(src include deps/http-parser) -FILE(GLOB SRC_HTTP deps/http-parser/*.c) +IF (WIN32 AND NOT MINGW) + ADD_DEFINITIONS(-DGIT_WINHTTP) +ELSE () + FIND_PACKAGE(OpenSSL) + FILE(GLOB SRC_HTTP deps/http-parser/*.c) +ENDIF() # Specify sha1 implementation IF (SHA1_TYPE STREQUAL "ppc") @@ -62,7 +67,7 @@ ENDIF() # Installation paths SET(INSTALL_BIN bin CACHE PATH "Where to install binaries to.") -SET(INSTALL_LIB lib CACHE PATH "Where to install libraries to.") +SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.") SET(INSTALL_INC include CACHE PATH "Where to install headers to.") # Build options @@ -75,7 +80,7 @@ OPTION (PROFILE "Generate profiling information" OFF) # Platform specific compilation flags IF (MSVC) - # Not using __stdcall with the CRT causes problems + # 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}") @@ -106,7 +111,6 @@ 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}) @@ -159,13 +163,18 @@ IF (MSVC) SET_SOURCE_FILES_PROPERTIES(src/win32/precompiled.c COMPILE_FLAGS "/Ycprecompiled.h") ENDIF () +# Backward compatibility with INSTALL_LIB variable +if (INSTALL_LIB) + set(LIB_INSTALL_DIR "${INSTALL_LIB}") +ENDIF() + # Install INSTALL(TARGETS git2 RUNTIME DESTINATION ${INSTALL_BIN} - LIBRARY DESTINATION ${INSTALL_LIB} - ARCHIVE DESTINATION ${INSTALL_LIB} + LIBRARY DESTINATION ${LIB_INSTALL_DIR} + ARCHIVE DESTINATION ${LIB_INSTALL_DIR} ) -INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${INSTALL_LIB}/pkgconfig ) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/libgit2.pc DESTINATION ${LIB_INSTALL_DIR}/pkgconfig ) INSTALL(DIRECTORY include/git2 DESTINATION ${INSTALL_INC} ) INSTALL(FILES include/git2.h DESTINATION ${INSTALL_INC} ) diff --git a/README.md b/README.md index 4c23fc8704b..0075e53dbea 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ For more advanced use or questions about CMake please read * GObject - * libgit2-glib + * libgit2-glib * Haskell * hgit2 * Lua diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000000..f2b6d7d234f --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +libgit2 examples +================ + +These examples are meant as thin, easy-to-read snippets for Docurium +(https://github.com/github/docurium) rather than full-blown +implementations of Git commands. They are not vetted as carefully +for bugs, error handling, or cross-platform compatibility as the +rest of the code in libgit2, so copy with some caution. + +For HTML versions, check "Examples" at http://libgit2.github.com/libgit2 + diff --git a/examples/network/Makefile b/examples/network/Makefile index 9afd49e5dc1..835be24ccd6 100644 --- a/examples/network/Makefile +++ b/examples/network/Makefile @@ -8,6 +8,7 @@ OBJECTS = \ git2.o \ ls-remote.o \ fetch.o \ + clone.o \ index-pack.o all: $(OBJECTS) diff --git a/examples/network/clone.c b/examples/network/clone.c new file mode 100644 index 00000000000..fb571bd3afa --- /dev/null +++ b/examples/network/clone.c @@ -0,0 +1,68 @@ +#include "common.h" +#include +#include +#include +#include +#include +#include +#include + +struct dl_data { + git_indexer_stats fetch_stats; + git_indexer_stats checkout_stats; + git_checkout_opts opts; + int ret; + int finished; + const char *url; + const char *path; +}; + +static void *clone_thread(void *ptr) +{ + struct dl_data *data = (struct dl_data *)ptr; + git_repository *repo = NULL; + + // 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; + + pthread_exit(&data->ret); +} + +int do_clone(git_repository *repo, int argc, char **argv) +{ + struct dl_data data = {0}; + pthread_t worker; + + // Validate args + if (argc < 3) { + printf("USAGE: %s \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); + + // 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; +} + diff --git a/examples/network/common.h b/examples/network/common.h index 29460bb3650..c82eaa1c86d 100644 --- a/examples/network/common.h +++ b/examples/network/common.h @@ -10,5 +10,6 @@ int parse_pkt_line(git_repository *repo, int argc, char **argv); int show_remote(git_repository *repo, int argc, char **argv); int fetch(git_repository *repo, int argc, char **argv); int index_pack(git_repository *repo, int argc, char **argv); +int do_clone(git_repository *repo, int argc, char **argv); #endif /* __COMMON_H__ */ diff --git a/examples/network/fetch.c b/examples/network/fetch.c index d2752124d38..fa941b97adf 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -4,6 +4,7 @@ #include #include #include +#include struct dl_data { git_remote *remote; @@ -13,6 +14,13 @@ struct dl_data { int finished; }; +static void progress_cb(const char *str, int len, void *data) +{ + data = data; + printf("remote: %.*s", len, str); + fflush(stdout); /* We don't have the \n to force the flush */ +} + static void *download(void *ptr) { struct dl_data *data = (struct dl_data *)ptr; @@ -39,10 +47,10 @@ static void *download(void *ptr) pthread_exit(&data->ret); } -int update_cb(const char *refname, const git_oid *a, const git_oid *b) +static int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data) { - const char *action; char a_str[GIT_OID_HEXSZ+1], b_str[GIT_OID_HEXSZ+1]; + data = data; git_oid_fmt(b_str, b); b_str[GIT_OID_HEXSZ] = '\0'; @@ -65,7 +73,9 @@ int fetch(git_repository *repo, int argc, char **argv) git_indexer_stats 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]); if (git_remote_load(&remote, repo, argv[1]) < 0) { @@ -73,6 +83,12 @@ int fetch(git_repository *repo, int argc, char **argv) return -1; } + // Set up the callbacks (only update_tips for now) + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.update_tips = &update_cb; + callbacks.progress = &progress_cb; + git_remote_set_callbacks(remote, &callbacks); + // Set up the information for the background worker thread data.remote = remote; data.bytes = &bytes; @@ -89,10 +105,17 @@ int fetch(git_repository *repo, int argc, char **argv) // the download rate. do { usleep(10000); - printf("\rReceived %d/%d objects in %d bytes", stats.processed, stats.total, bytes); + + if (stats.total > 0) + printf("Received %d/%d objects (%d) in %d bytes\r", + stats.received, stats.total, stats.processed, bytes); } while (!data.finished); - printf("\rReceived %d/%d objects in %d bytes\n", stats.processed, stats.total, bytes); + 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); // Disconnect the underlying connection to prevent from idling. git_remote_disconnect(remote); @@ -101,7 +124,7 @@ int fetch(git_repository *repo, int argc, char **argv) // right commits. This may be needed even if there was no packfile // to download, which can happen e.g. when the branches have been // changed but all the neede objects are available locally. - if (git_remote_update_tips(remote, update_cb) < 0) + if (git_remote_update_tips(remote) < 0) return -1; git_remote_free(remote); diff --git a/examples/network/git2.c b/examples/network/git2.c index 7c02305c465..ecb16630b65 100644 --- a/examples/network/git2.c +++ b/examples/network/git2.c @@ -1,5 +1,6 @@ #include #include +#include #include "common.h" @@ -12,11 +13,12 @@ struct { } commands[] = { {"ls-remote", ls_remote}, {"fetch", fetch}, + {"clone", do_clone}, {"index-pack", index_pack}, { NULL, NULL} }; -int run_command(git_cb fn, int argc, char **argv) +static int run_command(git_cb fn, int argc, char **argv) { int error; git_repository *repo; @@ -45,7 +47,7 @@ int run_command(git_cb fn, int argc, char **argv) int main(int argc, char **argv) { - int i, error; + int i; if (argc < 2) { fprintf(stderr, "usage: %s [repo]\n", argv[0]); diff --git a/examples/network/index-pack.c b/examples/network/index-pack.c index ef5a359573d..85aac4aff86 100644 --- a/examples/network/index-pack.c +++ b/examples/network/index-pack.c @@ -2,13 +2,20 @@ #include #include #include +#include +#include +#include +#include #include "common.h" // This could be run in the main loop whilst the application waits for // the indexing to finish in a worker thread -int index_cb(const git_indexer_stats *stats, void *data) +static int index_cb(const git_indexer_stats *stats, void *data) { + data = data; printf("\rProcessing %d of %d", stats->processed, stats->total); + + return 0; } int index_pack(git_repository *repo, int argc, char **argv) @@ -20,6 +27,7 @@ int index_pack(git_repository *repo, int argc, char **argv) ssize_t read_bytes; char buf[512]; + repo = repo; if (argc < 2) { fprintf(stderr, "I need a packfile\n"); return EXIT_FAILURE; @@ -43,7 +51,7 @@ int index_pack(git_repository *repo, int argc, char **argv) if ((error = git_indexer_stream_add(idx, buf, read_bytes, &stats)) < 0) goto cleanup; - printf("\rIndexing %d of %d", stats.processed, stats.total); + index_cb(&stats, NULL); } while (read_bytes > 0); if (read_bytes < 0) { @@ -65,38 +73,3 @@ int index_pack(git_repository *repo, int argc, char **argv) git_indexer_stream_free(idx); return error; } - -int index_pack_old(git_repository *repo, int argc, char **argv) -{ - git_indexer *indexer; - git_indexer_stats stats; - int error; - char hash[GIT_OID_HEXSZ + 1] = {0}; - - if (argc < 2) { - fprintf(stderr, "I need a packfile\n"); - return EXIT_FAILURE; - } - - // Create a new indexer - error = git_indexer_new(&indexer, argv[1]); - if (error < 0) - return error; - - // Index the packfile. This function can take a very long time and - // should be run in a worker thread. - error = git_indexer_run(indexer, &stats); - if (error < 0) - return error; - - // Write the information out to an index file - error = git_indexer_write(indexer); - - // Get the packfile's hash (which should become it's filename) - git_oid_fmt(hash, git_indexer_hash(indexer)); - puts(hash); - - git_indexer_free(indexer); - - return 0; -} diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 39cc6472558..822d6f66818 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -7,12 +7,14 @@ static int show_ref__cb(git_remote_head *head, void *payload) { char oid[GIT_OID_HEXSZ + 1] = {0}; + + payload = payload; git_oid_fmt(oid, &head->oid); printf("%s\t%s\n", oid, head->name); return 0; } -int use_unnamed(git_repository *repo, const char *url) +static int use_unnamed(git_repository *repo, const char *url) { git_remote *remote = NULL; int error; @@ -37,7 +39,7 @@ int use_unnamed(git_repository *repo, const char *url) return error; } -int use_remote(git_repository *repo, char *name) +static int use_remote(git_repository *repo, char *name) { git_remote *remote = NULL; int error; @@ -63,8 +65,9 @@ int use_remote(git_repository *repo, char *name) int ls_remote(git_repository *repo, int argc, char **argv) { - int error, i; + int error; + argc = argc; /* If there's a ':' in the name, assume it's an URL */ if (strchr(argv[1], ':') != NULL) { error = use_unnamed(repo, argv[1]); diff --git a/include/git2.h b/include/git2.h index f260cfacde4..805044abbd2 100644 --- a/include/git2.h +++ b/include/git2.h @@ -37,7 +37,12 @@ #include "git2/index.h" #include "git2/config.h" #include "git2/remote.h" +#include "git2/clone.h" +#include "git2/checkout.h" +#include "git2/attr.h" +#include "git2/ignore.h" +#include "git2/branch.h" #include "git2/refspec.h" #include "git2/net.h" #include "git2/status.h" diff --git a/include/git2/attr.h b/include/git2/attr.h index 8f5a1268d13..2de9f4b0ee7 100644 --- a/include/git2/attr.h +++ b/include/git2/attr.h @@ -30,7 +30,7 @@ GIT_BEGIN_DECL * Then for file `xyz.c` looking up attribute "foo" gives a value for * which `GIT_ATTR_TRUE(value)` is true. */ -#define GIT_ATTR_TRUE(attr) ((attr) == git_attr__true) +#define GIT_ATTR_TRUE(attr) (git_attr_value(attr) == GIT_ATTR_TRUE_T) /** * GIT_ATTR_FALSE checks if an attribute is set off. In core git @@ -44,7 +44,7 @@ GIT_BEGIN_DECL * Then for file `zyx.h` looking up attribute "foo" gives a value for * which `GIT_ATTR_FALSE(value)` is true. */ -#define GIT_ATTR_FALSE(attr) ((attr) == git_attr__false) +#define GIT_ATTR_FALSE(attr) (git_attr_value(attr) == GIT_ATTR_FALSE_T) /** * GIT_ATTR_UNSPECIFIED checks if an attribute is unspecified. This @@ -62,7 +62,7 @@ GIT_BEGIN_DECL * file `onefile.rb` or looking up "bar" on any file will all give * `GIT_ATTR_UNSPECIFIED(value)` of true. */ -#define GIT_ATTR_UNSPECIFIED(attr) (!(attr) || (attr) == git_attr__unset) +#define GIT_ATTR_UNSPECIFIED(attr) (git_attr_value(attr) == GIT_ATTR_UNSPECIFIED_T) /** * GIT_ATTR_HAS_VALUE checks if an attribute is set to a value (as @@ -74,13 +74,29 @@ GIT_BEGIN_DECL * Given this, looking up "eol" for `onefile.txt` will give back the * string "lf" and `GIT_ATTR_SET_TO_VALUE(attr)` will return true. */ -#define GIT_ATTR_HAS_VALUE(attr) \ - ((attr) && (attr) != git_attr__unset && \ - (attr) != git_attr__true && (attr) != git_attr__false) +#define GIT_ATTR_HAS_VALUE(attr) (git_attr_value(attr) == GIT_ATTR_VALUE_T) -GIT_EXTERN(const char *) git_attr__true; -GIT_EXTERN(const char *) git_attr__false; -GIT_EXTERN(const char *) git_attr__unset; +typedef enum { + GIT_ATTR_UNSPECIFIED_T = 0, + GIT_ATTR_TRUE_T, + GIT_ATTR_FALSE_T, + GIT_ATTR_VALUE_T, +} git_attr_t; + +/* + * Return the value type for a given attribute. + * + * This can be either `TRUE`, `FALSE`, `UNSPECIFIED` (if the attribute + * was not set at all), or `VALUE`, if the attribute was set to + * an actual string. + * + * If the attribute has a `VALUE` string, it can be accessed normally + * as a NULL-terminated C string. + * + * @param attr The attribute + * @return the value type for the attribute + */ +GIT_EXTERN(git_attr_t) git_attr_value(const char *attr); /** * Check attribute flags: Reading values from index and working directory. @@ -172,18 +188,17 @@ GIT_EXTERN(int) git_attr_get_many( * * @param repo The repository containing the path. * @param flags A combination of GIT_ATTR_CHECK... flags. - * @param path The path inside the repo to check attributes. This - * does not have to exist, but if it does not, then - * it will be treated as a plain file (i.e. not a directory). - * @param callback The function that will be invoked on each attribute - * and attribute value. The name parameter will be the name - * of the attribute and the value will be the value it is - * set to, including possibly NULL if the attribute is - * explicitly set to UNSPECIFIED using the ! sign. This - * will be invoked only once per attribute name, even if - * there are multiple rules for a given file. The highest - * priority rule will be used. + * @param path Path inside the repo to check attributes. This does not have + * to exist, but if it does not, then it will be treated as a + * plain file (i.e. not a directory). + * @param callback Function to invoke on each attribute name and value. The + * value may be NULL is the attribute is explicitly set to + * UNSPECIFIED using the '!' sign. Callback will be invoked + * only once per attribute name, even if there are multiple + * rules for a given file. The highest priority rule will be + * used. Return a non-zero value from this to stop looping. * @param payload Passed on as extra parameter to callback function. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_attr_foreach( git_repository *repo, diff --git a/include/git2/blob.h b/include/git2/blob.h index 544dc7c410c..f0719f15d9d 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -46,7 +46,7 @@ GIT_INLINE(int) git_blob_lookup(git_blob **blob, git_repository *repo, const git * @param len the length of the short identifier * @return 0 or an error code */ -GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, unsigned int len) +GIT_INLINE(int) git_blob_lookup_prefix(git_blob **blob, git_repository *repo, const git_oid *id, size_t len) { return git_object_lookup_prefix((git_object **)blob, repo, id, len, GIT_OBJ_BLOB); } diff --git a/include/git2/branch.h b/include/git2/branch.h index 8884df15a97..f072799c5ac 100644 --- a/include/git2/branch.h +++ b/include/git2/branch.h @@ -8,6 +8,7 @@ #define INCLUDE_git_branch_h__ #include "common.h" +#include "oid.h" #include "types.h" /** @@ -26,9 +27,9 @@ GIT_BEGIN_DECL * this target commit. If `force` is true and a reference * already exists with the given name, it'll be replaced. * - * @param oid_out Pointer where to store the OID of the target commit. + * The returned reference must be freed by the user. * - * @param repo Repository where to store the branch. + * @param branch_out Pointer where to store the underlying reference. * * @param branch_name Name for the branch; this name is * validated for consistency. It should also not conflict with @@ -46,7 +47,7 @@ GIT_BEGIN_DECL * pointing to the provided target commit. */ GIT_EXTERN(int) git_branch_create( - git_oid *oid_out, + git_reference **branch_out, git_repository *repo, const char *branch_name, const git_object *target, @@ -55,25 +56,19 @@ GIT_EXTERN(int) git_branch_create( /** * Delete an existing branch reference. * - * @param repo Repository where lives the branch. + * If the branch is successfully deleted, the passed reference + * object will be freed and invalidated. * - * @param branch_name Name of the branch to be deleted; - * this name is validated for consistency. - * - * @param branch_type Type of the considered branch. This should - * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE. - * - * @return 0 on success, GIT_ENOTFOUND if the branch - * doesn't exist or an error code. + * @param branch A valid reference representing a branch + * @return 0 on success, or an error code. */ -GIT_EXTERN(int) git_branch_delete( - git_repository *repo, - const char *branch_name, - git_branch_t branch_type); +GIT_EXTERN(int) git_branch_delete(git_reference *branch); /** * Loop over all the branches 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 branches. * * @param list_flags Filtering flags for the branch @@ -84,7 +79,7 @@ GIT_EXTERN(int) git_branch_delete( * * @param payload Extra parameter to callback function. * - * @return 0 or an error code. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_branch_foreach( git_repository *repo, @@ -97,27 +92,62 @@ GIT_EXTERN(int) git_branch_foreach( ); /** - * Move/rename an existing branch reference. - * - * @param repo Repository where lives the branch. + * Move/rename an existing local branch reference. * - * @param old_branch_name Current name of the branch to be moved; - * this name is validated for consistency. + * @param branch Current underlying reference of the branch. * * @param new_branch_name Target name of the branch once the move * is performed; this name is validated for consistency. * * @param force Overwrite existing branch. * - * @return 0 on success, GIT_ENOTFOUND if the branch - * doesn't exist or an error code. + * @return 0 on success, or an error code. */ GIT_EXTERN(int) git_branch_move( - git_repository *repo, - const char *old_branch_name, + git_reference *branch, const char *new_branch_name, int force); +/** + * Lookup a branch by its name in a repository. + * + * The generated reference must be freed by the user. + * + * @param branch_out pointer to the looked-up branch reference + * + * @param repo the repository to look up the branch + * + * @param branch_name Name of the branch to be looked-up; + * this name is validated for consistency. + * + * @param branch_type Type of the considered branch. This should + * be valued with either GIT_BRANCH_LOCAL or GIT_BRANCH_REMOTE. + * + * @return 0 on success; GIT_ENOTFOUND when no matching branch + * exists, otherwise an error code. + */ +GIT_EXTERN(int) git_branch_lookup( + git_reference **branch_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type); + +/** + * Return the reference supporting the remote tracking branch, + * given a local branch reference. + * + * @param tracking_out Pointer where to store the retrieved + * reference. + * + * @param branch Current underlying reference of the branch. + * + * @return 0 on success; GIT_ENOTFOUND when no remote tracking + * reference exists, otherwise an error code. + */ +GIT_EXTERN(int) git_branch_tracking( + git_reference **tracking_out, + git_reference *branch); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/checkout.h b/include/git2/checkout.h new file mode 100644 index 00000000000..ef3badbe994 --- /dev/null +++ b/include/git2/checkout.h @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_checkout_h__ +#define INCLUDE_git_checkout_h__ + +#include "common.h" +#include "types.h" +#include "indexer.h" + + +/** + * @file git2/checkout.h + * @brief Git checkout routines + * @defgroup git_checkout Git checkout routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +enum { + GIT_CHECKOUT_DEFAULT = (1 << 0), + GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1), + GIT_CHECKOUT_CREATE_MISSING = (1 << 2), + GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3), +}; + +/* Use zeros to indicate default settings */ +typedef struct git_checkout_opts { + unsigned int checkout_strategy; /* default: GIT_CHECKOUT_DEFAULT */ + int disable_filters; + int dir_mode; /* default is 0755 */ + int file_mode; /* default is 0644 */ + 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. + */ + int (* skipped_notify_cb)( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload); + + void *notify_payload; + + /* when not NULL, arrays of fnmatch pattern specifying + * which paths should be taken into account + */ + git_strarray paths; +} git_checkout_opts; + +/** + * Updates files in the index and the working tree to match the content of the + * commit pointed at by HEAD. + * + * @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_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); + +/** + * Updates files in the working tree to match the content of the index. + * + * @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_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); + +/** + * Updates files in the index and working tree to match the content of the + * tree pointed at by the treeish. + * + * @param repo repository to check out (must be non-bare) + * @param treeish a commit, tag or tree which content will be used to update + * the working directory + * @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_END_DECL +#endif diff --git a/include/git2/clone.h b/include/git2/clone.h new file mode 100644 index 00000000000..40292ed59f6 --- /dev/null +++ b/include/git2/clone.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_clone_h__ +#define INCLUDE_git_clone_h__ + +#include "common.h" +#include "types.h" +#include "indexer.h" +#include "checkout.h" + + +/** + * @file git2/clone.h + * @brief Git cloning routines + * @defgroup git_clone Git cloning routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * Clone a remote repository, and checkout the branch pointed to by the remote + * HEAD. + * + * @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 checkout_opts options for the checkout step (may be NULL) + * @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); + +/** + * Create a bare clone of a remote repository. + * + * @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) + * @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_END_DECL +#endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 640adf5c233..a159b79e1dc 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -48,7 +48,7 @@ GIT_INLINE(int) git_commit_lookup(git_commit **commit, git_repository *repo, con * @param len the length of the short identifier * @return 0 or an error code */ -GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, unsigned len) +GIT_INLINE(int) git_commit_lookup_prefix(git_commit **commit, git_repository *repo, const git_oid *id, size_t len) { return git_object_lookup_prefix((git_object **)commit, repo, id, len, GIT_OBJ_COMMIT); } @@ -178,6 +178,25 @@ GIT_EXTERN(int) git_commit_parent(git_commit **parent, git_commit *commit, unsig */ GIT_EXTERN(const git_oid *) git_commit_parent_oid(git_commit *commit, unsigned int n); +/** + * Get the commit object that is the th generation ancestor + * of the named commit object, following only the first parents. + * The returned commit has to be freed by the caller. + * + * Passing `0` as the generation number returns another instance of the + * base commit itself. + * + * @param ancestor Pointer where to store the ancestor commit + * @param commit a previously loaded commit. + * @param n the requested generation + * @return 0 on success; GIT_ENOTFOUND if no matching ancestor exists + * or an error code + */ +GIT_EXTERN(int) git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n); + /** * Create a new commit in the repository using `git_object` * instances as parameters. diff --git a/include/git2/common.h b/include/git2/common.h index 1af045cffc3..0af37e81f36 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -103,6 +103,29 @@ GIT_EXTERN(int) git_strarray_copy(git_strarray *tgt, const git_strarray *src); */ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); +/** + * Combinations of these values describe the capabilities of libgit2. + */ +enum { + GIT_CAP_THREADS = ( 1 << 0 ), + GIT_CAP_HTTPS = ( 1 << 1 ) +}; + +/** + * Query compile time options for libgit2. + * + * @return A combination of GIT_CAP_* values. + * + * - GIT_CAP_THREADS + * Libgit2 was compiled with thread support. Note that thread support is still to be seen as a + * 'work in progress'. + * + * - GIT_CAP_HTTPS + * Libgit2 supports the https:// protocol. This requires the open ssl library to be + * found when compiling libgit2. + */ +GIT_EXTERN(int) git_libgit2_capabilities(void); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/config.h b/include/git2/config.h index 36946c4a511..21d8a0b05dd 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -33,7 +33,7 @@ struct git_config_file { int (*set)(struct git_config_file *, const char *key, const char *value); int (*set_multivar)(git_config_file *cfg, const char *name, const char *regexp, const char *value); int (*del)(struct git_config_file *, const char *key); - int (*foreach)(struct git_config_file *, int (*fn)(const char *, const char *, void *), void *data); + int (*foreach)(struct git_config_file *, const char *, int (*fn)(const char *, const char *, void *), void *data); void (*free)(struct git_config_file *); }; @@ -80,15 +80,16 @@ GIT_EXTERN(int) git_config_find_global(char *global_config_path, size_t length); GIT_EXTERN(int) git_config_find_system(char *system_config_path, size_t length); /** - * Open the global configuration file + * Open the global and system configuration files * - * Utility wrapper that calls `git_config_find_global` - * and opens the located file, if it exists. + * Utility wrapper that finds the global and system configuration files + * and opens them into a single prioritized config object that can be + * used when accessing default config data outside a repository. * * @param out Pointer to store the config instance * @return 0 or an error code */ -GIT_EXTERN(int) git_config_open_global(git_config **out); +GIT_EXTERN(int) git_config_open_default(git_config **out); /** * Create a configuration file backend for ondisk files @@ -302,18 +303,36 @@ GIT_EXTERN(int) git_config_delete(git_config *cfg, const char *name); * The callback receives the normalized name and value of each variable * in the config backend, and the data pointer passed to this function. * As soon as one of the callback functions returns something other than 0, - * this function returns that value. + * this function stops iterating and returns `GIT_EUSER`. * * @param cfg where to get the variables from * @param callback the function to call on each variable * @param payload the data to pass to the callback - * @return 0 or the return value of the callback which didn't return 0 + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_config_foreach( git_config *cfg, int (*callback)(const char *var_name, const char *value, void *payload), void *payload); +/** + * Perform an operation on each config variable matching a regular expression. + * + * This behaviors like `git_config_foreach` with an additional filter of a + * regular expression that filters which config keys are passed to the + * callback. + * + * @param cfg where to get the variables from + * @param regexp regular expression to match against config names + * @param callback the function to call on each variable + * @param payload the data to pass to the callback + * @return 0 or the return value of the callback which didn't return 0 + */ +GIT_EXTERN(int) git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*callback)(const char *var_name, const char *value, void *payload), + void *payload); /** * Query the value of a config variable and return it mapped to @@ -324,7 +343,7 @@ GIT_EXTERN(int) git_config_foreach( * * A mapping array looks as follows: * - * git_cvar_map autocrlf_mapping[3] = { + * git_cvar_map autocrlf_mapping[] = { * {GIT_CVAR_FALSE, NULL, GIT_AUTO_CRLF_FALSE}, * {GIT_CVAR_TRUE, NULL, GIT_AUTO_CRLF_TRUE}, * {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT}, diff --git a/include/git2/diff.h b/include/git2/diff.h index d4d0eac474c..05825c50db8 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -46,6 +46,7 @@ enum { GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), + GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11), }; /** @@ -55,15 +56,22 @@ enum { * values. Similarly, passing NULL for the options structure will * give the defaults. The default values are marked below. * - * @todo Most of the parameters here are not actually supported at this time. + * - flags: a combination of the GIT_DIFF_... 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") + * - new_prefix: "directory" to prefix to new file names (default "b") + * - pathspec: array of paths / patterns to constrain diff + * - max_size: maximum blob size to diff, above this treated as binary */ typedef struct { uint32_t flags; /**< defaults to GIT_DIFF_NORMAL */ uint16_t context_lines; /**< defaults to 3 */ - uint16_t interhunk_lines; /**< defaults to 3 */ + uint16_t interhunk_lines; /**< defaults to 0 */ char *old_prefix; /**< defaults to "a" */ char *new_prefix; /**< defaults to "b" */ git_strarray pathspec; /**< defaults to show all paths */ + git_off_t max_size; /**< defaults to 512Mb */ } git_diff_options; /** @@ -71,13 +79,28 @@ typedef struct { */ typedef struct git_diff_list git_diff_list; +/** + * Flags that can be set for the file on side of a diff. + * + * Most of the flags are just for internal consumption by libgit2, + * but some of them may be interesting to external users. They are: + * + * - VALID_OID - the `oid` value is computed and correct + * - FREE_PATH - the `path` string is separated allocated memory + * - BINARY - this file should be considered binary data + * - NOT_BINARY - this file should be considered text data + * - FREE_DATA - the internal file data is kept in allocated memory + * - UNMAP_DATA - the internal file data is kept in mmap'ed memory + * - NO_DATA - this side of the diff should not be loaded + */ enum { GIT_DIFF_FILE_VALID_OID = (1 << 0), GIT_DIFF_FILE_FREE_PATH = (1 << 1), GIT_DIFF_FILE_BINARY = (1 << 2), GIT_DIFF_FILE_NOT_BINARY = (1 << 3), GIT_DIFF_FILE_FREE_DATA = (1 << 4), - GIT_DIFF_FILE_UNMAP_DATA = (1 << 5) + GIT_DIFF_FILE_UNMAP_DATA = (1 << 5), + GIT_DIFF_FILE_NO_DATA = (1 << 6), }; /** @@ -168,7 +191,7 @@ enum { GIT_DIFF_LINE_CONTEXT = ' ', GIT_DIFF_LINE_ADDITION = '+', GIT_DIFF_LINE_DELETION = '-', - GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< DEPRECATED */ + GIT_DIFF_LINE_ADD_EOFNL = '\n', /**< Removed line w/o LF & added one with */ GIT_DIFF_LINE_DEL_EOFNL = '\0', /**< LF was removed at end of file */ /* The following values will only be sent to a `git_diff_data_fn` when @@ -196,6 +219,11 @@ typedef int (*git_diff_data_fn)( const char *content, size_t content_len); +/** + * The diff iterator object is used to scan a diff list. + */ +typedef struct git_diff_iterator git_diff_iterator; + /** @name Diff List Generator Functions * * These are the functions you would use to create (or destroy) a @@ -331,6 +359,9 @@ GIT_EXTERN(int) git_diff_merge( * callbacks will not be invoked for binary files on the diff list or for * files whose only changed is a file mode change. * + * Returning a non-zero value from any of the callbacks will terminate + * the iteration and cause this return `GIT_EUSER`. + * * @param diff A git_diff_list generated by one of the above functions. * @param cb_data Reference pointer that will be passed to your callbacks. * @param file_cb Callback function to make per file in the diff. @@ -340,6 +371,7 @@ GIT_EXTERN(int) git_diff_merge( * @param line_cb Optional callback to make per line of diff text. This * same callback will be made for context lines, added, and * removed lines, and even for a deleted trailing newline. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_foreach( git_diff_list *diff, @@ -348,8 +380,147 @@ GIT_EXTERN(int) git_diff_foreach( git_diff_hunk_fn hunk_cb, git_diff_data_fn line_cb); +/** + * Create a diff iterator object that can be used to traverse a diff. + * + * This iterator can be used instead of `git_diff_foreach` in situations + * where callback functions are awkward to use. Because of the way that + * diffs are calculated internally, using an iterator will use somewhat + * more memory than `git_diff_foreach` would. + * + * @param iterator Output parameter of newly created iterator. + * @param diff Diff over which you wish to iterate. + * @return 0 on success, < 0 on error + */ +GIT_EXTERN(int) git_diff_iterator_new( + git_diff_iterator **iterator, + git_diff_list *diff); + +/** + * Release the iterator object. + * + * Call this when you are done using the iterator. + * + * @param iterator The diff iterator to be freed. + */ +GIT_EXTERN(void) git_diff_iterator_free(git_diff_iterator *iterator); + +/** + * Return progress value for traversing the diff. + * + * This returns a value between 0.0 and 1.0 that represents the progress + * through the diff iterator. The value is monotonically increasing and + * will advance gradually as you progress through the iteration. + * + * @param iterator The diff iterator + * @return Value between 0.0 and 1.0 + */ +GIT_EXTERN(float) git_diff_iterator_progress(git_diff_iterator *iterator); + +/** + * Return the number of hunks in the current file + * + * This will return the number of diff hunks in the current file. If the + * diff has not been performed yet, this may result in loading the file and + * performing the diff. + * + * @param iterator The iterator object + * @return The number of hunks in the current file or <0 on loading failure + */ +GIT_EXTERN(int) git_diff_iterator_num_hunks_in_file(git_diff_iterator *iterator); + +/** + * Return the number of lines in the hunk currently being examined. + * + * This will return the number of lines in the current hunk. If the diff + * has not been performed yet, this may result in loading the file and + * performing the diff. + * + * @param iterator The iterator object + * @return The number of lines in the current hunk (context, added, and + * removed all added together) or <0 on loading failure + */ +GIT_EXTERN(int) git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iterator); + +/** + * Return the delta information for the next file in the diff. + * + * This will return a pointer to the next git_diff_delta` to be processed or + * NULL if the iterator is at the end of the diff, then advance. This + * returns the value `GIT_ITEROVER` after processing the last file. + * + * @param delta Output parameter for the next delta object + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done, other value < 0 on error + */ +GIT_EXTERN(int) git_diff_iterator_next_file( + git_diff_delta **delta, + git_diff_iterator *iterator); + +/** + * Return the hunk information for the next hunk in the current file. + * + * It is recommended that you not call this if the file is a binary + * file, but it is allowed to do so. + * + * The `header` text output will contain the standard hunk header that + * would appear in diff output. The header string will be NUL terminated. + * + * WARNING! Call this function for the first time on a file is when the + * actual text diff will be computed (it cannot be computed incrementally) + * so the first call for a new file is expensive (at least in relative + * terms - in reality, it is still pretty darn fast). + * + * @param range Output pointer to range of lines covered by the hunk; + * This range object is owned by the library and should not be freed. + * @param header Output pointer to the text of the hunk header + * This string is owned by the library and should not be freed. + * @param header_len Output pointer to store the length of the header text + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done with current file, other + * value < 0 on error + */ +GIT_EXTERN(int) git_diff_iterator_next_hunk( + git_diff_range **range, + const char **header, + size_t *header_len, + git_diff_iterator *iterator); + +/** + * Return the next line of the current hunk of diffs. + * + * The `line_origin` output will tell you what type of line this is + * (e.g. was it added or removed or is it just context for the diff). + * + * The `content` will be a pointer to the file data that goes in the + * line. IT WILL NOT BE NUL TERMINATED. You have to use the `content_len` + * value and only process that many bytes of data from the content string. + * + * @param line_origin Output pointer to store a GIT_DIFF_LINE value for this + * next chunk of data. The value is a single character, not a buffer. + * @param content Output pointer to store the content of the diff; this + * string is owned by the library and should not be freed. + * @param content_len Output pointer to store the length of the content. + * @param iterator The iterator object + * @return 0 on success, GIT_ITEROVER when done with current line, other + * value < 0 on error + */ +GIT_EXTERN(int) git_diff_iterator_next_line( + char *line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char **content, + size_t *content_len, + git_diff_iterator *iterator); + /** * Iterate over a diff generating text output like "git diff --name-status". + * + * Returning a non-zero value from the callbacks will terminate the + * iteration and cause this return `GIT_EUSER`. + * + * @param diff A git_diff_list generated by one of the above functions. + * @param cb_data Reference pointer that will be passed to your callback. + * @param print_cb Callback to make per line of diff text. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_print_compact( git_diff_list *diff, @@ -361,6 +532,9 @@ GIT_EXTERN(int) git_diff_print_compact( * * This is a super easy way to generate a patch from a diff. * + * Returning a non-zero value from the callbacks will terminate the + * iteration and cause this return `GIT_EUSER`. + * * @param diff A git_diff_list generated by one of the above functions. * @param cb_data Reference pointer that will be passed to your callbacks. * @param print_cb Callback function to output lines of the diff. This @@ -368,12 +542,28 @@ GIT_EXTERN(int) git_diff_print_compact( * headers, and diff lines. Fortunately, you can probably * use various GIT_DIFF_LINE constants to determine what * text you are given. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_print_patch( git_diff_list *diff, void *cb_data, git_diff_data_fn print_cb); +/** + * Query how many diff records are there in a diff list. + * + * You can optionally pass in a `git_delta_t` value if you want a count + * of just entries that match that delta type, or pass -1 for all delta + * records. + * + * @param diff A git_diff_list generated by one of the above functions + * @param delta_t A git_delta_t value to filter the count, or -1 for all records + * @return Count of number of deltas matching delta_t type + */ +GIT_EXTERN(int) git_diff_entrycount( + git_diff_list *diff, + int delta_t); + /**@}*/ @@ -392,6 +582,8 @@ GIT_EXTERN(int) git_diff_print_patch( * When at least one of the blobs being dealt with is binary, the * `git_diff_delta` binary attribute will be set to 1 and no call to the * hunk_cb nor line_cb will be made. + * + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_diff_blobs( git_blob *old_blob, diff --git a/include/git2/errors.h b/include/git2/errors.h index ca7f0de6e70..e5f435926ce 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -25,9 +25,11 @@ enum { GIT_EEXISTS = -4, GIT_EAMBIGUOUS = -5, GIT_EBUFS = -6, + GIT_EUSER = -7, + GIT_EBAREREPO = -8, GIT_PASSTHROUGH = -30, - GIT_REVWALKOVER = -31, + GIT_ITEROVER = -31, }; typedef struct { @@ -53,6 +55,7 @@ typedef enum { GITERR_TREE, GITERR_INDEXER, GITERR_SSL, + GITERR_SUBMODULE, } git_error_t; /** diff --git a/include/git2/ignore.h b/include/git2/ignore.h new file mode 100644 index 00000000000..f7e04e881a4 --- /dev/null +++ b/include/git2/ignore.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_ignore_h__ +#define INCLUDE_git_ignore_h__ + +#include "common.h" +#include "types.h" + +GIT_BEGIN_DECL + +/** + * Add ignore rules for a repository. + * + * Excludesfile rules (i.e. .gitignore rules) are generally read from + * .gitignore files in the repository tree or from a shared system file + * only if a "core.excludesfile" config value is set. The library also + * keeps a set of per-repository internal ignores that can be configured + * in-memory and will not persist. This function allows you to add to + * that internal rules list. + * + * Example usage: + * + * error = git_ignore_add(myrepo, "*.c\ndir/\nFile with space\n"); + * + * This would add three rules to the ignores. + * + * @param repo The repository to add ignore rules to. + * @param rules Text of rules, a la the contents of a .gitignore file. + * It is okay to have multiple rules in the text; if so, + * each rule should be terminated with a newline. + * @return 0 on success + */ +GIT_EXTERN(int) git_ignore_add_rule( + git_repository *repo, + const char *rules); + +/** + * Clear ignore rules that were explicitly added. + * + * Clears the internal ignore rules that have been set up. This will not + * turn off the rules in .gitignore files that actually exist in the + * filesystem. + * + * @param repo The repository to remove ignore rules from. + * @return 0 on success + */ +GIT_EXTERN(int) git_ignore_clear_internal_rules( + git_repository *repo); + +/** + * Test if the ignore rules apply to a given path. + * + * This function simply checks the ignore rules to see if they would apply + * to the given file. This indicates if the file would be ignored regardless + * of whether the file is already in the index or commited to the repository. + * + * @param ignored boolean returning 0 if the file is not ignored, 1 if it is + * @param repo a repository object + * @param path the file to check ignores for, relative to the repo's workdir. + * @return 0 if ignore rules could be processed for the file (regardless + * of whether it exists or not), or an error < 0 if they could not. + */ +GIT_EXTERN(int) git_ignore_path_is_ignored( + int *ignored, + git_repository *repo, + const char *path); + +GIT_END_DECL + +#endif diff --git a/include/git2/index.h b/include/git2/index.h index f863a60650f..062932e1aa4 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -8,6 +8,7 @@ #define INCLUDE_git_index_h__ #include "common.h" +#include "indexer.h" #include "types.h" #include "oid.h" @@ -279,7 +280,7 @@ GIT_EXTERN(int) git_index_remove(git_index *index, int position); * @param n the position of the entry * @return a pointer to the entry; NULL if out of bounds */ -GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, unsigned int n); +GIT_EXTERN(git_index_entry *) git_index_get(git_index *index, size_t n); /** * Get the count of entries currently in the index @@ -319,7 +320,7 @@ GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_bypath(git_i * @param n the position of the entry * @return a pointer to the unmerged entry; NULL if out of bounds */ -GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, unsigned int n); +GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_index *index, size_t n); /** * Return the stage number from a git index entry @@ -335,15 +336,17 @@ GIT_EXTERN(const git_index_entry_unmerged *) git_index_get_unmerged_byindex(git_ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); /** - * Read a tree into the index file + * Read a tree into the index file with stats * - * The current index contents will be replaced by the specified tree. + * The current index contents will be replaced by the specified tree. The total + * node count is collected in stats. * * @param index an existing index object * @param tree tree to read + * @param stats structure that receives the total node count (may be NULL) * @return 0 or an error code */ -GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree); +GIT_EXTERN(int) git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats); /** @} */ GIT_END_DECL diff --git a/include/git2/indexer.h b/include/git2/indexer.h index d300ba01a22..87f48fe277c 100644 --- a/include/git2/indexer.h +++ b/include/git2/indexer.h @@ -19,6 +19,7 @@ GIT_BEGIN_DECL typedef struct git_indexer_stats { unsigned int total; unsigned int processed; + unsigned int received; } git_indexer_stats; diff --git a/include/git2/merge.h b/include/git2/merge.h index c80803d3673..37b1c787d41 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -28,7 +28,7 @@ GIT_BEGIN_DECL * @param one one of the commits * @param two the other commit */ -GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two); +GIT_EXTERN(int) git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two); /** * Find a merge base given a list of commits diff --git a/include/git2/message.h b/include/git2/message.h index 7f2558583c5..b42cb76777f 100644 --- a/include/git2/message.h +++ b/include/git2/message.h @@ -23,8 +23,9 @@ GIT_BEGIN_DECL * * Optionally, can remove lines starting with a "#". * - * @param message_out The user allocated buffer which will be filled with - * the cleaned up message. + * @param message_out The user allocated buffer which will be filled with + * the cleaned up message. Pass NULL if you just want to get the size of the + * prettified message as the output value. * * @param size The size of the allocated buffer message_out. * @@ -32,7 +33,8 @@ GIT_BEGIN_DECL * * @param strip_comments 1 to remove lines starting with a "#", 0 otherwise. * - * @return GIT_SUCCESS or an error code + * @return -1 on error, else number of characters in prettified message + * including the trailing NUL byte */ GIT_EXTERN(int) git_message_prettify(char *message_out, size_t buffer_size, const char *message, int strip_comments); diff --git a/include/git2/notes.h b/include/git2/notes.h index 19073abd12a..af480a40818 100644 --- a/include/git2/notes.h +++ b/include/git2/notes.h @@ -23,10 +23,11 @@ GIT_BEGIN_DECL * * The note must be freed manually by the user. * - * @param note the note; NULL in case of error - * @param repo the Git repository - * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" - * @param oid OID of the object + * @param note pointer to the read note; NULL in case of error + * @param repo repository where to look up the note + * @param notes_ref canonical name of the reference to use (optional); + * defaults to "refs/notes/commits" + * @param oid OID of the git object to read the note from * * @return 0 or an error code */ @@ -50,17 +51,17 @@ GIT_EXTERN(const char *) git_note_message(git_note *note); */ GIT_EXTERN(const git_oid *) git_note_oid(git_note *note); - /** * Add a note for an object * - * @param oid pointer to store the OID (optional); NULL in case of error - * @param repo the Git repository + * @param out pointer to store the OID (optional); NULL in case of error + * @param repo repository where to store the note * @param author signature of the notes commit author * @param committer signature of the notes commit committer - * @param notes_ref OID reference to update (optional); defaults to "refs/notes/commits" - * @param oid The OID of the object - * @param oid The note to add for object oid + * @param notes_ref canonical name of the reference to use (optional); + * defaults to "refs/notes/commits" + * @param oid OID of the git object to decorate + * @param note Content of the note to add for object oid * * @return 0 or an error code */ @@ -73,11 +74,12 @@ GIT_EXTERN(int) git_note_create(git_oid *out, git_repository *repo, /** * Remove the note for an object * - * @param repo the Git repository - * @param notes_ref OID reference to use (optional); defaults to "refs/notes/commits" + * @param repo repository where the note lives + * @param notes_ref canonical name of the reference to use (optional); + * defaults to "refs/notes/commits" * @param author signature of the notes commit author * @param committer signature of the notes commit committer - * @param oid the oid which note's to be removed + * @param oid OID of the git object to remove the note from * * @return 0 or an error code */ @@ -119,19 +121,21 @@ typedef struct { * * @param repo Repository where to find the notes. * - * @param notes_ref OID reference to read from (optional); defaults to "refs/notes/commits". + * @param notes_ref Reference to read from (optional); defaults to + * "refs/notes/commits". * - * @param note_cb Callback to invoke per found annotation. + * @param note_cb Callback to invoke per found annotation. Return non-zero + * to stop looping. * * @param payload Extra parameter to callback function. * - * @return 0 or an error code. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_note_foreach( - git_repository *repo, - const char *notes_ref, - int (*note_cb)(git_note_data *note_data, void *payload), - void *payload + git_repository *repo, + const char *notes_ref, + int (*note_cb)(git_note_data *note_data, void *payload), + void *payload ); /** @} */ diff --git a/include/git2/object.h b/include/git2/object.h index 414325121d4..fd6ae95c142 100644 --- a/include/git2/object.h +++ b/include/git2/object.h @@ -75,7 +75,7 @@ GIT_EXTERN(int) git_object_lookup_prefix( git_object **object_out, git_repository *repo, const git_oid *id, - unsigned int len, + size_t len, git_otype type); /** @@ -167,6 +167,26 @@ GIT_EXTERN(int) git_object_typeisloose(git_otype type); */ GIT_EXTERN(size_t) git_object__size(git_otype type); +/** + * Recursively peel an object until an object of the specified type is met. + * + * The retrieved `peeled` object is owned by the repository and should be + * closed with the `git_object_free` method. + * + * If you pass `GIT_OBJ_ANY` as the target type, then the object will be + * peeled until the type changes (e.g. a tag will be chased until the + * referenced object is no longer a tag). + * + * @param peeled Pointer to the peeled git_object + * @param object The object to be processed + * @param target_type The type of the requested object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type); + /** @} */ GIT_END_DECL diff --git a/include/git2/odb.h b/include/git2/odb.h index e2443178c99..c6e73571b27 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -139,7 +139,7 @@ GIT_EXTERN(int) git_odb_read(git_odb_object **out, git_odb *db, const git_oid *i * GIT_ENOTFOUND if the object is not in the database. * GIT_EAMBIGUOUS if the prefix is ambiguous (several objects match the prefix) */ -GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len); +GIT_EXTERN(int) git_odb_read_prefix(git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len); /** * Read the header of an object from the database, without @@ -172,6 +172,21 @@ GIT_EXTERN(int) git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *d */ GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id); +/** + * List all objects available in the database + * + * The callback will be called for each object available in the + * database. Note that the objects are likely to be returned in the index + * order, which would make accessing the objects in that order inefficient. + * Return a non-zero value from the callback to stop looping. + * + * @param db database to use + * @param cb the callback to call for each object + * @param data data to pass to the callback + * @return 0 on success, GIT_EUSER on non-zero callback, or error code + */ +GIT_EXTERN(int) git_odb_foreach(git_odb *db, int (*cb)(git_oid *oid, void *data), void *data); + /** * Write an object directly into the ODB * @@ -264,8 +279,10 @@ GIT_EXTERN(int) git_odb_hash(git_oid *id, const void *data, size_t len, git_otyp /** * Read a file from disk and fill a git_oid with the object id * that the file would have if it were written to the Object - * Database as an object of the given type. Similar functionality - * to git.git's `git hash-object` without the `-w` flag. + * Database as an object of the given type (w/o applying filters). + * Similar functionality to git.git's `git hash-object` without + * the `-w` flag, however, with the --no-filters flag. + * If you need filters, see git_repository_hashfile. * * @param out oid structure the result is written into. * @param path file to read and determine object id for diff --git a/include/git2/odb_backend.h b/include/git2/odb_backend.h index f4620f5f4f9..cb806978782 100644 --- a/include/git2/odb_backend.h +++ b/include/git2/odb_backend.h @@ -26,6 +26,10 @@ struct git_odb_stream; struct git_odb_backend { git_odb *odb; + /* read and read_prefix each return to libgit2 a buffer which + * will be freed later. The buffer should be allocated using + * the function git_odb_backend_malloc to ensure that it can + * be safely freed later. */ int (* read)( void **, size_t *, git_otype *, struct git_odb_backend *, @@ -42,7 +46,7 @@ struct git_odb_backend { void **, size_t *, git_otype *, struct git_odb_backend *, const git_oid *, - unsigned int); + size_t); int (* read_header)( size_t *, git_otype *, @@ -71,6 +75,12 @@ 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 + ); + void (* free)(struct git_odb_backend *); }; @@ -94,6 +104,9 @@ struct git_odb_stream { 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); + +GIT_EXTERN(void *) git_odb_backend_malloc(git_odb_backend *backend, size_t len); GIT_END_DECL diff --git a/include/git2/oid.h b/include/git2/oid.h index a05b40a3798..9e54a9f963e 100644 --- a/include/git2/oid.h +++ b/include/git2/oid.h @@ -136,7 +136,31 @@ GIT_EXTERN(void) git_oid_cpy(git_oid *out, const git_oid *src); * @param b second oid structure. * @return <0, 0, >0 if a < b, a == b, a > b. */ -GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b); +GIT_INLINE(int) git_oid_cmp(const git_oid *a, const git_oid *b) +{ + const unsigned char *sha1 = a->id; + const unsigned char *sha2 = b->id; + int i; + + for (i = 0; i < GIT_OID_RAWSZ; i++, sha1++, sha2++) { + if (*sha1 != *sha2) + return *sha1 - *sha2; + } + + return 0; +} + +/** + * Compare two oid structures for equality + * + * @param a first oid structure. + * @param b second oid structure. + * @return true if equal, false otherwise + */ +GIT_INLINE(int) git_oid_equal(const git_oid *a, const git_oid *b) +{ + return !git_oid_cmp(a, b); +} /** * Compare the first 'len' hexadecimal characters (packets of 4 bits) @@ -147,7 +171,7 @@ GIT_EXTERN(int) git_oid_cmp(const git_oid *a, const git_oid *b); * @param len the number of hex chars to compare * @return 0 in case of a match */ -GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, unsigned int len); +GIT_EXTERN(int) git_oid_ncmp(const git_oid *a, const git_oid *b, size_t len); /** * Check if an oid equals an hex formatted object id. @@ -161,6 +185,8 @@ GIT_EXTERN(int) git_oid_streq(const git_oid *a, const char *str); /** * Check is an oid is all zeros. + * + * @return 1 if all zeros, 0 otherwise. */ GIT_EXTERN(int) git_oid_iszero(const git_oid *a); diff --git a/include/git2/reflog.h b/include/git2/reflog.h index f490e29dea8..447915ef8f6 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -23,6 +23,10 @@ GIT_BEGIN_DECL /** * Read the reflog for the given reference * + * If there is no reflog file for the given + * reference yet, an empty reflog object will + * be returned. + * * The reflog must be freed manually by using * git_reflog_free(). * @@ -33,26 +37,32 @@ GIT_BEGIN_DECL GIT_EXTERN(int) git_reflog_read(git_reflog **reflog, git_reference *ref); /** - * Write a new reflog for the given reference + * Write an existing in-memory reflog object back to disk + * using an atomic file lock. * - * If there is no reflog file for the given - * reference yet, it will be created. - * - * `oid_old` may be NULL in case it's a new reference. + * @param reflog an existing reflog object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reflog_write(git_reflog *reflog); + +/** + * Add a new entry to the reflog. * * `msg` is optional and can be NULL. * - * @param ref the changed reference - * @param oid_old the OID the reference was pointing to + * @param reflog an existing reflog object + * @param new_oid the OID the reference is now pointing to * @param committer the signature of the committer * @param msg the reflog message * @return 0 or an error code */ -GIT_EXTERN(int) git_reflog_write(git_reference *ref, const git_oid *oid_old, const git_signature *committer, const char *msg); +GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *new_oid, const git_signature *committer, const char *msg); /** * Rename the reflog for the given reference * + * The reflog to be renamed is expected to already exist + * * @param ref the reference * @param new_name the new name of the reference * @return 0 or an error code @@ -82,7 +92,27 @@ GIT_EXTERN(unsigned int) git_reflog_entrycount(git_reflog *reflog); * @param idx the position to lookup * @return the entry; NULL if not found */ -GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx); +GIT_EXTERN(const git_reflog_entry *) git_reflog_entry_byindex(git_reflog *reflog, size_t idx); + +/** + * 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`. + * + * @param reflog a previously loaded reflog. + * + * @param idx the position of the entry to remove. + * + * @param rewrite_previous_entry 1 to rewrite the history; 0 otherwise. + * + * @return 0 on success or an error code. + */ +GIT_EXTERN(int) git_reflog_drop( + git_reflog *reflog, + unsigned int idx, + int rewrite_previous_entry); /** * Get the old oid diff --git a/include/git2/refs.h b/include/git2/refs.h index 2aa0ac267a1..73b32a9e2b2 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -268,14 +268,15 @@ GIT_EXTERN(int) git_reference_list(git_strarray *array, git_repository *repo, un * * 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. + * 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 callback Function which will be called for every listed ref * @param payload Additional data to pass to the callback - * @return 0 or an error code + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_reference_foreach(git_repository *repo, unsigned int list_flags, int (*callback)(const char *, void *), void *payload); @@ -334,6 +335,8 @@ GIT_EXTERN(int) git_reference_cmp(git_reference *ref1, git_reference *ref2); * * @param repo Repository where to find the references. * + * @param glob Glob pattern references should match. + * * @param list_flags Filtering flags for the reference * listing. * @@ -353,6 +356,104 @@ GIT_EXTERN(int) git_reference_foreach_glob( void *payload ); +/** + * Check if a reflog exists for the specified reference. + * + * @param ref A git reference + * + * @return 0 when no reflog can be found, 1 when it exists; + * otherwise an error code. + */ +GIT_EXTERN(int) git_reference_has_log(git_reference *ref); + +/** + * Check if a reference is a local branch. + * + * @param ref A git reference + * + * @return 1 when the reference lives in the refs/heads + * namespace; 0 otherwise. + */ +GIT_EXTERN(int) git_reference_is_branch(git_reference *ref); + +/** + * Check if a reference is a remote tracking branch + * + * @param ref A git reference + * + * @return 1 when the reference lives in the refs/remotes + * namespace; 0 otherwise. + */ +GIT_EXTERN(int) git_reference_is_remote(git_reference *ref); + +enum { + GIT_REF_FORMAT_NORMAL = 0, + + /** + * Control whether one-level refnames are accepted + * (i.e., refnames that do not contain multiple /-separated + * components) + */ + GIT_REF_FORMAT_ALLOW_ONELEVEL = (1 << 0), + + /** + * Interpret the provided name as a reference pattern for a + * refspec (as used with remote repositories). If this option + * is enabled, the name is allowed to contain a single * () + * in place of a one full pathname component + * (e.g., foo//bar but not foo/bar). + */ + GIT_REF_FORMAT_REFSPEC_PATTERN = (1 << 1), +}; + +/** + * 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. + * + * TODO: Implement handling of GIT_REF_FORMAT_REFSPEC_PATTERN + * + * @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. + */ +GIT_EXTERN(int) git_reference_normalize_name( + char *buffer_out, + size_t buffer_size, + const char *name, + unsigned int flags); + +/** + * Recursively peel an reference until an object of the + * specified type is met. + * + * The retrieved `peeled` object is owned by the repository + * and should be closed with the `git_object_free` method. + * + * If you pass `GIT_OBJ_ANY` as the target type, then the object + * will be peeled until a non-tag object is met. + * + * @param peeled Pointer to the peeled git_object + * @param ref The reference to be processed + * @param target_type The type of the requested object + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reference_peel( + git_object **out, + git_reference *ref, + git_otype type); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/remote.h b/include/git2/remote.h index 5c01949d265..a3913af5bca 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -79,6 +79,36 @@ GIT_EXTERN(const char *) git_remote_name(git_remote *remote); */ GIT_EXTERN(const char *) git_remote_url(git_remote *remote); +/** + * Get the remote's url for pushing + * + * @param remote the remote + * @return a pointer to the url or NULL if no special url for pushing is set + */ +GIT_EXTERN(const char *) git_remote_pushurl(git_remote *remote); + +/** + * Set the remote's url + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_url(git_remote *remote, const char* url); + +/** + * Set the remote's url for pushing + * + * Existing connections will not be updated. + * + * @param remote the remote + * @param url the url to set or NULL to clear the pushurl + * @return 0 or an error value + */ +GIT_EXTERN(int) git_remote_set_pushurl(git_remote *remote, const char* url); + /** * Set the remote's fetch refspec * @@ -133,9 +163,12 @@ GIT_EXTERN(int) git_remote_connect(git_remote *remote, int direction); * The remote (or more exactly its transport) must be connected. The * memory belongs to the remote. * + * If you a return a non-zero value from the callback, this will stop + * looping over the refs. + * * @param refs where to store the refs * @param remote the remote - * @return 0 or an error code + * @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); @@ -190,7 +223,7 @@ GIT_EXTERN(void) git_remote_free(git_remote *remote); * @param remote the remote to update * @param cb callback to run on each ref update. 'a' is the old value, 'b' is then new value */ -GIT_EXTERN(int) git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)); +GIT_EXTERN(int) git_remote_update_tips(git_remote *remote); /** * Return whether a string is a valid remote URL @@ -238,6 +271,39 @@ GIT_EXTERN(int) git_remote_add(git_remote **out, git_repository *repo, const cha GIT_EXTERN(void) git_remote_check_cert(git_remote *remote, int check); +/** + * Argument to the completion callback which tells it which operation + * finished. + */ +typedef enum git_remote_completion_type { + GIT_REMOTE_COMPLETION_DOWNLOAD, + GIT_REMOTE_COMPLETION_INDEXING, + GIT_REMOTE_COMPLETION_ERROR, +} git_remote_completion_type; + +/** + * The callback settings structure + * + * Set the calbacks to be called by the remote. + */ +struct git_remote_callbacks { + void (*progress)(const char *str, int len, void *data); + int (*completion)(git_remote_completion_type type, void *data); + int (*update_tips)(const char *refname, const git_oid *a, const git_oid *b, void *data); + void *data; +}; + +/** + * Set the callbacks for a remote + * + * Note that the remote keeps its own copy of the data and you need to + * call this function again if you want to change the callbacks. + * + * @param remote the remote to configure + * @param callbacks a pointer to the user's callback settings + */ +GIT_EXTERN(void) git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/repository.h b/include/git2/repository.h index 0b56a0870ba..025a0a95dec 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -35,6 +35,19 @@ GIT_BEGIN_DECL */ GIT_EXTERN(int) git_repository_open(git_repository **repository, const char *path); +/** + * Create a "fake" repository to wrap an object database + * + * Create a repository object to wrap an object database to be used + * with the API when all you have is an object database. This doesn't + * have any paths associated with it, so use with care. + * + * @param repository pointer to the repo + * @param odb the object database to wrap + * @return 0 or an error code + */ +GIT_EXTERN(int) git_repository_wrap_odb(git_repository **repository, git_odb *odb); + /** * Look for a git repository and copy its path in the given buffer. * The lookup start from base_path and walk across parent directories @@ -70,6 +83,18 @@ GIT_EXTERN(int) git_repository_discover( int across_fs, const char *ceiling_dirs); +/** + * Option flags for `git_repository_open_ext`. + * + * * GIT_REPOSITORY_OPEN_NO_SEARCH - Only open the repository if it can be + * immediately found in the start_path. Do not walk up from the + * start_path looking at parent directories. + * * GIT_REPOSITORY_OPEN_CROSS_FS - Unless this flag is set, open will not + * continue searching across filesystem boundaries (i.e. when `st_dev` + * changes from the `stat` system call). (E.g. Searching in a user's home + * directory "/home/user/source/" will not return "/.git/" as the found + * repo if "/" is a different filesystem than "/home".) + */ enum { GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), @@ -77,6 +102,20 @@ enum { /** * Find and open a repository with extended controls. + * + * @param repo_out Pointer to the repo which will be opened. This can + * actually be NULL if you only want to use the error code to + * see if a repo at this path could be opened. + * @param start_path Path to open as git repository. If the flags + * permit "searching", then this can be a path to a subdirectory + * inside the working directory of the repository. + * @param flags A combination of the GIT_REPOSITORY_OPEN flags above. + * @param ceiling_dirs A GIT_PATH_LIST_SEPARATOR delimited list of path + * prefixes at which the search for a containing repository should + * terminate. + * @return 0 on success, GIT_ENOTFOUND if no repository could be found, + * or -1 if there was a repository but open failed for some reason + * (such as repo corruption or system errors). */ GIT_EXTERN(int) git_repository_open_ext( git_repository **repo, @@ -105,13 +144,127 @@ GIT_EXTERN(void) git_repository_free(git_repository *repo); * * @param repo_out pointer to the repo which will be created or reinitialized * @param path the path to the repository - * @param is_bare if true, a Git repository without a working directory is created - * at the pointed path. If false, provided path will be considered as the working - * directory into which the .git directory will be created. + * @param is_bare if true, a Git repository without a working directory is + * created at the pointed path. If false, provided path will be + * considered as the working directory into which the .git directory + * will be created. * * @return 0 or an error code */ -GIT_EXTERN(int) git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare); +GIT_EXTERN(int) git_repository_init( + git_repository **repo_out, + const char *path, + unsigned is_bare); + +/** + * Option flags for `git_repository_init_ext`. + * + * These flags configure extra behaviors to `git_repository_init_ext`. + * In every case, the default behavior is the zero value (i.e. flag is + * not set). Just OR the flag values together for the `flags` parameter + * when initializing a new repo. Details of individual values are: + * + * * BARE - Create a bare repository with no working directory. + * * NO_REINIT - Return an EEXISTS error if the repo_path appears to + * already be an git repository. + * * NO_DOTGIT_DIR - Normally a "/.git/" will be appended to the repo + * path for non-bare repos (if it is not already there), but + * passing this flag prevents that behavior. + * * MKDIR - Make the repo_path (and workdir_path) as needed. Init is + * always willing to create the ".git" directory even without this + * flag. This flag tells init to create the trailing component of + * the repo and workdir paths as needed. + * * MKPATH - Recursively make all components of the repo and workdir + * paths as necessary. + * * EXTERNAL_TEMPLATE - libgit2 normally uses internal templates to + * initialize a new repo. This flags enables external templates, + * looking the "template_path" from the options if set, or the + * `init.templatedir` global config if not, or falling back on + * "/usr/share/git-core/templates" if it exists. + */ +enum { + GIT_REPOSITORY_INIT_BARE = (1u << 0), + GIT_REPOSITORY_INIT_NO_REINIT = (1u << 1), + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = (1u << 2), + GIT_REPOSITORY_INIT_MKDIR = (1u << 3), + GIT_REPOSITORY_INIT_MKPATH = (1u << 4), + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1u << 5), +}; + +/** + * Mode options for `git_repository_init_ext`. + * + * Set the mode field of the `git_repository_init_options` structure + * either to the custom mode that you would like, or to one of the + * following modes: + * + * * SHARED_UMASK - Use permissions configured by umask - the default. + * * SHARED_GROUP - Use "--shared=group" behavior, chmod'ing the new repo + * to be group writable and "g+sx" for sticky group assignment. + * * SHARED_ALL - Use "--shared=all" behavior, adding world readability. + * * Anything else - Set to custom value. + */ +enum { + GIT_REPOSITORY_INIT_SHARED_UMASK = 0, + GIT_REPOSITORY_INIT_SHARED_GROUP = 0002775, + GIT_REPOSITORY_INIT_SHARED_ALL = 0002777, +}; + +/** + * Extended options structure for `git_repository_init_ext`. + * + * This contains extra options for `git_repository_init_ext` that enable + * additional initialization features. The fields are: + * + * * flags - Combination of GIT_REPOSITORY_INIT flags above. + * * mode - Set to one of the standard GIT_REPOSITORY_INIT_SHARED_... + * constants above, or to a custom value that you would like. + * * workdir_path - The path to the working dir or NULL for default (i.e. + * repo_path parent on non-bare repos). IF THIS IS RELATIVE PATH, + * IT WILL BE EVALUATED RELATIVE TO THE REPO_PATH. If this is not + * the "natural" working directory, a .git gitlink file will be + * created here linking to the repo_path. + * * description - If set, this will be used to initialize the "description" + * file in the repository, instead of using the template content. + * * template_path - When GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE is set, + * this contains the path to use for the template directory. If + * this is NULL, the config or default directory options will be + * used instead. + * * initial_head - The name of the head to point HEAD at. If NULL, then + * this will be treated as "master" and the HEAD ref will be set + * to "refs/heads/master". If this begins with "refs/" it will be + * used verbatim; otherwise "refs/heads/" will be prefixed. + * * origin_url - If this is non-NULL, then after the rest of the + * repository initialization is completed, an "origin" remote + * will be added pointing to this URL. + */ +typedef struct { + uint32_t flags; + uint32_t mode; + const char *workdir_path; + const char *description; + const char *template_path; + const char *initial_head; + const char *origin_url; +} git_repository_init_options; + +/** + * Create a new Git repository in the given folder with extended controls. + * + * This will initialize a new git repository (creating the repo_path + * if requested by flags) and working directory as needed. It will + * auto-detect the case sensitivity of the file system and if the + * file system supports file mode bits correctly. + * + * @param repo_out Pointer to the repo which will be created or reinitialized. + * @param repo_path The path to the repository. + * @param opts Pointer to git_repository_init_options struct. + * @return 0 or an error code on failure. + */ +GIT_EXTERN(int) git_repository_init_ext( + git_repository **repo_out, + const char *repo_path, + git_repository_init_options *opts); /** * Retrieve and resolve the reference pointed at by HEAD. @@ -194,9 +347,12 @@ GIT_EXTERN(const char *) git_repository_workdir(git_repository *repo); * * @param repo A repository object * @param workdir The path to a working directory + * @param update_gitlink Create/update gitlink in workdir and set config + * "core.worktree" (if workdir is not the parent of the .git directory) * @return 0, or an error code */ -GIT_EXTERN(int) git_repository_set_workdir(git_repository *repo, const char *workdir); +GIT_EXTERN(int) git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink); /** * Check if a repository is bare @@ -299,6 +455,118 @@ GIT_EXTERN(int) git_repository_index(git_index **out, git_repository *repo); */ GIT_EXTERN(void) git_repository_set_index(git_repository *repo, git_index *index); +/** + * Retrieve git's prepared message + * + * Operations such as git revert/cherry-pick/merge with the -n option + * stop just short of creating a commit with the changes and save + * their prepared message in .git/MERGE_MSG so the next git-commit + * execution can present it to the user for them to amend if they + * wish. + * + * Use this function to get the contents of this file. Don't forget to + * remove the file after you create the commit. + * + * @param buffer Buffer to write data into or NULL to just read required size + * @param len Length of buffer in bytes + * @param repo Repository to read prepared message from + * @return Bytes written to buffer, GIT_ENOTFOUND if no message, or -1 on error + */ +GIT_EXTERN(int) git_repository_message(char *buffer, size_t len, git_repository *repo); + +/** + * Remove git's prepared message. + * + * Remove the message that `git_repository_message` retrieves. + */ +GIT_EXTERN(int) git_repository_message_remove(git_repository *repo); + +/** + * Calculate hash of file using repository filtering rules. + * + * If you simply want to calculate the hash of a file on disk with no filters, + * you can just use the `git_odb_hashfile()` API. However, if you want to + * hash a file in the repository and you want to apply filtering rules (e.g. + * crlf filters) before generating the SHA, then use this function. + * + * @param out Output value of calculated SHA + * @param repo Repository pointer + * @param path Path to file on disk whose contents should be hashed. If the + * repository is not NULL, this can be a relative path. + * @param type The object type to hash as (e.g. GIT_OBJ_BLOB) + * @param as_path The path to use to look up filtering rules. If this is + * NULL, then the `path` parameter will be used instead. If + * this is passed as the empty string, then no filters will be + * applied when calculating the hash. + */ +GIT_EXTERN(int) git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_otype type, + const char *as_path); + +/** + * Make the repository HEAD point to the specified reference. + * + * If the provided reference points to a Tree or a Blob, the HEAD is + * unaltered and -1 is returned. + * + * If the provided reference points to a branch, the HEAD will point + * to that branch, staying attached, or become attached if it isn't yet. + * If the branch doesn't exist yet, no error will be return. The HEAD + * will then be attached to an unborn branch. + * + * Otherwise, the HEAD will be detached and will directly point to + * the Commit. + * + * @param repo Repository pointer + * @param refname Canonical name of the reference the HEAD should point at + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head( + git_repository* repo, + const char* refname); + +/** + * Make the repository HEAD directly point to the Commit. + * + * If the provided committish cannot be found in the repository, the HEAD + * is unaltered and GIT_ENOTFOUND is returned. + * + * If the provided commitish cannot be peeled into a commit, the HEAD + * is unaltered and -1 is returned. + * + * Otherwise, the HEAD will eventually be detached and will directly point to + * the peeled Commit. + * + * @param repo Repository pointer + * @param commitish Object id of the Commit the HEAD should point to + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish); + +/** + * Detach the HEAD. + * + * If the HEAD is already detached and points to a Commit, 0 is returned. + * + * If the HEAD is already detached and points to a Tag, the HEAD is + * updated into making it point to the peeled Commit, and 0 is returned. + * + * If the HEAD is already detached and points to a non commitish, the HEAD is + * unaletered, and -1 is returned. + * + * Otherwise, the HEAD will be detached and point to the peeled Commit. + * + * @param repo Repository pointer + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_repository_detach_head( + git_repository* repo); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/reset.h b/include/git2/reset.h index 12517874853..cdcfb76710c 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -24,6 +24,9 @@ GIT_BEGIN_DECL * Specifying a Mixed kind of reset will trigger a Soft reset and the index will * be replaced with the content of the commit tree. * + * Specifying a Hard kind of reset will trigger a Mixed reset and the working + * directory will be replaced with the content of the index. + * * TODO: Implement remaining kinds of resets. * * @param repo Repository where to perform the reset operation. @@ -37,7 +40,7 @@ GIT_BEGIN_DECL * * @return GIT_SUCCESS or an error code */ -GIT_EXTERN(int) git_reset(git_repository *repo, const git_object *target, git_reset_type reset_type); +GIT_EXTERN(int) git_reset(git_repository *repo, git_object *target, git_reset_type reset_type); /** @} */ GIT_END_DECL diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 2e9dc421a92..0a85a4c6082 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -107,7 +107,7 @@ GIT_EXTERN(int) git_revwalk_push(git_revwalk *walk, const git_oid *oid); * The OIDs pointed to by the references that match the given glob * pattern will be pushed to the revision walker. * - * A leading 'refs/' is implied it not present as well as a trailing + * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * * @param walk the walker being used for the traversal @@ -146,7 +146,7 @@ GIT_EXTERN(int) git_revwalk_hide(git_revwalk *walk, const git_oid *oid); * pattern and their ancestors will be hidden from the output on the * revision walk. * - * A leading 'refs/' is implied it not present as well as a trailing + * A leading 'refs/' is implied if not present as well as a trailing * '/ *' if the glob lacks '?', '*' or '['. * * @param walk the walker being used for the traversal @@ -201,7 +201,7 @@ GIT_EXTERN(int) git_revwalk_hide_ref(git_revwalk *walk, const char *refname); * @param oid Pointer where to store the oid of the next commit * @param walk the walker to pop the commit from. * @return 0 if the next commit was found; - * GIT_REVWALKOVER if there are no commits left to iterate + * GIT_ITEROVER if there are no commits left to iterate */ GIT_EXTERN(int) git_revwalk_next(git_oid *oid, git_revwalk *walk); diff --git a/include/git2/signature.h b/include/git2/signature.h index cbf94269f2c..cdbe678795c 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -23,6 +23,9 @@ GIT_BEGIN_DECL * Create a new action signature. The signature must be freed * manually or using git_signature_free * + * Note: angle brackets ('<' and '>') characters are not allowed + * to be used in either the `name` or the `email` parameter. + * * @param sig_out new signature, in case of error NULL * @param name name of the person * @param email email of the person diff --git a/include/git2/status.h b/include/git2/status.h index 69b6e47e07e..cc94d768022 100644 --- a/include/git2/status.h +++ b/include/git2/status.h @@ -38,11 +38,11 @@ enum { * * The callback is passed the path of the file, the status and the data * pointer passed to this function. If the callback returns something other - * than 0, this function will return that value. + * than 0, this function will stop looping and return GIT_EUSER. * * @param repo a repository object * @param callback the function to call on each file - * @return 0 on success or the return value of the callback that was non-zero + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ GIT_EXTERN(int) git_status_foreach( git_repository *repo, @@ -96,6 +96,8 @@ typedef enum { * 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. */ enum { @@ -104,6 +106,7 @@ enum { 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), }; /** diff --git a/include/git2/submodule.h b/include/git2/submodule.h index f65911a3bdb..28057d26f6e 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -20,54 +20,153 @@ */ GIT_BEGIN_DECL +/** + * Opaque structure representing a submodule. + * + * Submodule support in libgit2 builds a list of known submodules and keeps + * it in the repository. The list is built from the .gitmodules file, the + * .git/config file, the index, and the HEAD tree. Items in the working + * directory that look like submodules (i.e. a git repo) but are not + * mentioned in those places won't be tracked. + */ +typedef struct git_submodule git_submodule; + +/** + * Values that could be specified for the update rule of a submodule. + * + * Use the DEFAULT value if you have altered the update value via + * `git_submodule_set_update()` and wish to reset to the original default. + */ typedef enum { + GIT_SUBMODULE_UPDATE_DEFAULT = -1, GIT_SUBMODULE_UPDATE_CHECKOUT = 0, GIT_SUBMODULE_UPDATE_REBASE = 1, - GIT_SUBMODULE_UPDATE_MERGE = 2 + GIT_SUBMODULE_UPDATE_MERGE = 2, + GIT_SUBMODULE_UPDATE_NONE = 3 } git_submodule_update_t; +/** + * Values that could be specified for how closely to examine the + * working directory when getting submodule status. + * + * Use the DEFUALT value if you have altered the ignore value via + * `git_submodule_set_ignore()` and wish to reset to the original value. + */ typedef enum { - GIT_SUBMODULE_IGNORE_ALL = 0, /* never dirty */ - GIT_SUBMODULE_IGNORE_DIRTY = 1, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_NONE = 3 /* any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_DEFAULT = -1, /* reset to default */ + GIT_SUBMODULE_IGNORE_NONE = 0, /* any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 1, /* dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 2, /* only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 3 /* never dirty */ } git_submodule_ignore_t; /** - * Description of submodule + * Return codes for submodule status. * - * This record describes a submodule found in a repository. There - * should be an entry for every submodule found in the HEAD and for - * every submodule described in .gitmodules. The fields are as follows: + * A combination of these flags will be returned to describe the status of a + * submodule. Depending on the "ignore" property of the submodule, some of + * the flags may never be returned because they indicate changes that are + * supposed to be ignored. * - * - `name` is the name of the submodule from .gitmodules. - * - `path` is the path to the submodule from the repo working directory. - * It is almost always the same as `name`. - * - `url` is the url for the submodule. - * - `oid` is the HEAD SHA1 for the submodule. - * - `update` is a value from above - see gitmodules(5) update. - * - `ignore` is a value from above - see gitmodules(5) ignore. - * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. - * - `refcount` is for internal use. + * Submodule info is contained in 4 places: the HEAD tree, the index, config + * files (both .git/config and .gitmodules), and the working directory. Any + * or all of those places might be missing information about the submodule + * depending on what state the repo is in. We consider all four places to + * build the combination of status flags. * - * If the submodule has been added to .gitmodules but not yet git added, - * then the `oid` will be zero. If the submodule has been deleted, but - * the delete has not been committed yet, then the `oid` will be set, but - * the `url` will be NULL. + * There are four values that are not really status, but give basic info + * about what sources of submodule data are available. These will be + * returned even if ignore is set to "ALL". + * + * * IN_HEAD - superproject head contains submodule + * * IN_INDEX - superproject index contains submodule + * * IN_CONFIG - superproject gitmodules has submodule + * * IN_WD - superproject workdir has submodule + * + * The following values will be returned so long as ignore is not "ALL". + * + * * INDEX_ADDED - in index, not in head + * * INDEX_DELETED - in head, not in index + * * INDEX_MODIFIED - index and head don't match + * * WD_UNINITIALIZED - workdir contains empty directory + * * WD_ADDED - in workdir, not index + * * WD_DELETED - in index, not workdir + * * WD_MODIFIED - index and workdir head don't match + * + * The following can only be returned if ignore is "NONE" or "UNTRACKED". + * + * * WD_INDEX_MODIFIED - submodule workdir index is dirty + * * WD_WD_MODIFIED - submodule workdir has modified files + * + * Lastly, the following will only be returned for ignore "NONE". + * + * * WD_UNTRACKED - wd contains untracked files */ -typedef struct { - char *name; - char *path; - char *url; - git_oid oid; /* sha1 of submodule HEAD ref or zero if not committed */ - git_submodule_update_t update; - git_submodule_ignore_t ignore; - int fetch_recurse; - int refcount; -} git_submodule; +typedef enum { + GIT_SUBMODULE_STATUS_IN_HEAD = (1u << 0), + GIT_SUBMODULE_STATUS_IN_INDEX = (1u << 1), + GIT_SUBMODULE_STATUS_IN_CONFIG = (1u << 2), + GIT_SUBMODULE_STATUS_IN_WD = (1u << 3), + GIT_SUBMODULE_STATUS_INDEX_ADDED = (1u << 4), + GIT_SUBMODULE_STATUS_INDEX_DELETED = (1u << 5), + GIT_SUBMODULE_STATUS_INDEX_MODIFIED = (1u << 6), + GIT_SUBMODULE_STATUS_WD_UNINITIALIZED = (1u << 7), + GIT_SUBMODULE_STATUS_WD_ADDED = (1u << 8), + GIT_SUBMODULE_STATUS_WD_DELETED = (1u << 9), + GIT_SUBMODULE_STATUS_WD_MODIFIED = (1u << 10), + GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED = (1u << 11), + GIT_SUBMODULE_STATUS_WD_WD_MODIFIED = (1u << 12), + GIT_SUBMODULE_STATUS_WD_UNTRACKED = (1u << 13), +} git_submodule_status_t; + +#define GIT_SUBMODULE_STATUS_IS_UNMODIFIED(S) \ + (((S) & ~(GIT_SUBMODULE_STATUS_IN_HEAD | GIT_SUBMODULE_STATUS_IN_INDEX | \ + GIT_SUBMODULE_STATUS_IN_CONFIG | GIT_SUBMODULE_STATUS_IN_WD)) == 0) /** - * Iterate over all submodules of a repository. + * Lookup submodule information by name or path. + * + * Given either the submodule name or path (they are usually the same), this + * returns a structure describing the submodule. + * + * There are two expected error scenarios: + * + * - The submodule is not mentioned in the HEAD, the index, and the config, + * but does "exist" in the working directory (i.e. there is a subdirectory + * that is a valid self-contained git repo). In this case, this function + * returns GIT_EEXISTS to indicate the the submodule exists but not in a + * state where a git_submodule can be instantiated. + * - The submodule is not mentioned in the HEAD, index, or config and the + * working directory doesn't contain a value git repo at that path. + * There may or may not be anything else at that path, but nothing that + * looks like a submodule. In this case, this returns GIT_ENOTFOUND. + * + * The submodule object is owned by the containing repo and will be freed + * when the repo is freed. The caller need not free the submodule. + * + * @param submodule Pointer to submodule description object pointer.. + * @param repo The repository. + * @param name The name of the submodule. Trailing slashes will be ignored. + * @return 0 on success, GIT_ENOTFOUND if submodule does not exist, + * GIT_EEXISTS if submodule exists in working directory only, -1 on + * other errors. + */ +GIT_EXTERN(int) git_submodule_lookup( + git_submodule **submodule, + git_repository *repo, + const char *name); + +/** + * Iterate over all tracked submodules of a repository. + * + * See the note on `git_submodule` above. This iterates over the tracked + * submodules as decribed therein. + * + * If you are concerned about items in the working directory that look like + * submodules but are not tracked, the diff API will generate a diff record + * for workdir items that look like submodules but are not tracked, showing + * them as added in the workdir. Also, the status API will treat the entire + * subdirectory of a contained git repo as a single GIT_STATUS_WT_NEW item. * * @param repo The repository * @param callback Function to be called with the name of each submodule. @@ -77,26 +176,326 @@ typedef struct { */ GIT_EXTERN(int) git_submodule_foreach( git_repository *repo, - int (*callback)(const char *name, void *payload), + int (*callback)(git_submodule *sm, const char *name, void *payload), void *payload); /** - * Lookup submodule information by name or path. + * Set up a new git submodule for checkout. * - * Given either the submodule name or path (they are usually the same), - * this returns a structure describing the submodule. If the submodule - * does not exist, this will return GIT_ENOTFOUND and set the submodule - * pointer to NULL. + * This does "git submodule add" up to the fetch and checkout of the + * submodule contents. It preps a new submodule, creates an entry in + * .gitmodules and creates an empty initialized repository either at the + * given path in the working directory or in .git/modules with a gitlink + * from the working directory to the new repo. * - * @param submodule Pointer to submodule description object pointer.. - * @param repo The repository. - * @param name The name of the submodule. Trailing slashes will be ignored. - * @return 0 on success, GIT_ENOTFOUND if submodule does not exist, -1 on error + * To fully emulate "git submodule add" call this function, then open the + * submodule repo and perform the clone step as needed. Lastly, call + * `git_submodule_add_finalize()` to wrap up adding the new submodule and + * .gitmodules to the index to be ready to commit. + * + * @param submodule The newly created submodule ready to open for clone + * @param repo Superproject repository to contain the new submodule + * @param url URL for the submodules remote + * @param path Path at which the submodule should be created + * @param use_gitlink Should workdir contain a gitlink to the repo in + * .git/modules vs. repo directly in workdir. + * @return 0 on success, GIT_EEXISTS if submodule already exists, + * -1 on other errors. */ -GIT_EXTERN(int) git_submodule_lookup( +GIT_EXTERN(int) git_submodule_add_setup( git_submodule **submodule, git_repository *repo, - const char *name); + const char *url, + const char *path, + int use_gitlink); + +/** + * Resolve the setup of a new git submodule. + * + * This should be called on a submodule once you have called add setup + * and done the clone of the submodule. This adds the .gitmodules file + * and the newly cloned submodule to the index to be ready to be committed + * (but doesn't actually do the commit). + * + * @param submodule The submodule to finish adding. + */ +GIT_EXTERN(int) git_submodule_add_finalize(git_submodule *submodule); + +/** + * Add current submodule HEAD commit to index of superproject. + * + * @param submodule The submodule to add to the index + * @param write_index Boolean if this should immediately write the index + * file. If you pass this as false, you will have to get the + * git_index and explicitly call `git_index_write()` on it to + * save the change. + * @return 0 on success, <0 on failure + */ +GIT_EXTERN(int) git_submodule_add_to_index( + git_submodule *submodule, + int write_index); + +/** + * Write submodule settings to .gitmodules file. + * + * This commits any in-memory changes to the submodule to the gitmodules + * file on disk. You may also be interested in `git_submodule_init()` which + * writes submodule info to ".git/config" (which is better for local changes + * to submodule settings) and/or `git_submodule_sync()` which writes + * settings about remotes to the actual submodule repository. + * + * @param submodule The submodule to write. + * @return 0 on success, <0 on failure. + */ +GIT_EXTERN(int) git_submodule_save(git_submodule *submodule); + +/** + * Get the containing repository for a submodule. + * + * This returns a pointer to the repository that contains the submodule. + * This is a just a reference to the repository that was passed to the + * original `git_submodule_lookup()` call, so if that repository has been + * freed, then this may be a dangling reference. + * + * @param submodule Pointer to submodule object + * @return Pointer to `git_repository` + */ +GIT_EXTERN(git_repository *) git_submodule_owner(git_submodule *submodule); + +/** + * Get the name of submodule. + * + * @param submodule Pointer to submodule object + * @return Pointer to the submodule name + */ +GIT_EXTERN(const char *) git_submodule_name(git_submodule *submodule); + +/** + * Get the path to the submodule. + * + * The path is almost always the same as the submodule name, but the + * two are actually not required to match. + * + * @param submodule Pointer to submodule object + * @return Pointer to the submodule path + */ +GIT_EXTERN(const char *) git_submodule_path(git_submodule *submodule); + +/** + * Get the URL for the submodule. + * + * @param submodule Pointer to submodule object + * @return Pointer to the submodule url + */ +GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule); + +/** + * Set the URL for the submodule. + * + * This sets the URL in memory for the submodule. This will be used for + * any following submodule actions while this submodule data is in memory. + * + * After calling this, you may wish to call `git_submodule_save()` to write + * the changes back to the ".gitmodules" file and `git_submodule_sync()` to + * write the changes to the checked out submodule repository. + * + * @param submodule Pointer to the submodule object + * @param url URL that should be used for the submodule + * @return 0 on success, <0 on failure + */ +GIT_EXTERN(int) git_submodule_set_url(git_submodule *submodule, const char *url); + +/** + * Get the OID for the submodule in the index. + * + * @param submodule Pointer to submodule object + * @return Pointer to git_oid or NULL if submodule is not in index. + */ +GIT_EXTERN(const git_oid *) git_submodule_index_oid(git_submodule *submodule); + +/** + * Get the OID for the submodule in the current HEAD tree. + * + * @param submodule Pointer to submodule object + * @return Pointer to git_oid or NULL if submodule is not in the HEAD. + */ +GIT_EXTERN(const git_oid *) git_submodule_head_oid(git_submodule *submodule); + +/** + * Get the OID for the submodule in the current working directory. + * + * This returns the OID that corresponds to looking up 'HEAD' in the checked + * out submodule. If there are pending changes in the index or anything + * else, this won't notice that. You should call `git_submodule_status()` + * for a more complete picture about the state of the working directory. + * + * @param submodule Pointer to submodule object + * @return Pointer to git_oid or NULL if submodule is not checked out. + */ +GIT_EXTERN(const git_oid *) git_submodule_wd_oid(git_submodule *submodule); + +/** + * Get the ignore rule for the submodule. + * + * There are four ignore values: + * + * - **GIT_SUBMODULE_IGNORE_NONE** will consider any change to the contents + * of the submodule from a clean checkout to be dirty, including the + * addition of untracked files. This is the default if unspecified. + * - **GIT_SUBMODULE_IGNORE_UNTRACKED** examines the contents of the + * working tree (i.e. call `git_status_foreach()` on the submodule) but + * UNTRACKED files will not count as making the submodule dirty. + * - **GIT_SUBMODULE_IGNORE_DIRTY** means to only check if the HEAD of the + * submodule has moved for status. This is fast since it does not need to + * scan the working tree of the submodule at all. + * - **GIT_SUBMODULE_IGNORE_ALL** means not to open the submodule repo. + * The working directory will be consider clean so long as there is a + * checked out version present. + */ +GIT_EXTERN(git_submodule_ignore_t) git_submodule_ignore( + git_submodule *submodule); + +/** + * Set the ignore rule for the submodule. + * + * This sets the ignore rule in memory for the submodule. This will be used + * for any following actions (such as `git_submodule_status()`) while the + * submodule is in memory. You should call `git_submodule_save()` if you + * want to persist the new ignore role. + * + * Calling this again with GIT_SUBMODULE_IGNORE_DEFAULT or calling + * `git_submodule_reload()` will revert the rule to the value that was in the + * original config. + * + * @return old value for ignore + */ +GIT_EXTERN(git_submodule_ignore_t) git_submodule_set_ignore( + git_submodule *submodule, + git_submodule_ignore_t ignore); + +/** + * Get the update rule for the submodule. + */ +GIT_EXTERN(git_submodule_update_t) git_submodule_update( + git_submodule *submodule); + +/** + * Set the update rule for the submodule. + * + * This sets the update rule in memory for the submodule. You should call + * `git_submodule_save()` if you want to persist the new update rule. + * + * Calling this again with GIT_SUBMODULE_UPDATE_DEFAULT or calling + * `git_submodule_reload()` will revert the rule to the value that was in the + * original config. + * + * @return old value for update + */ +GIT_EXTERN(git_submodule_update_t) git_submodule_set_update( + git_submodule *submodule, + git_submodule_update_t update); + +/** + * Read the fetchRecurseSubmodules rule for a submodule. + * + * This accesses the submodule..fetchRecurseSubmodules value for + * the submodule that controls fetching behavior for the submodule. + * + * Note that at this time, libgit2 does not honor this setting and the + * fetch functionality current ignores submodules. + * + * @return 0 if fetchRecurseSubmodules is false, 1 if true + */ +GIT_EXTERN(int) git_submodule_fetch_recurse_submodules( + git_submodule *submodule); + +/** + * Set the fetchRecurseSubmodules rule for a submodule. + * + * This sets the submodule..fetchRecurseSubmodules value for + * the submodule. You should call `git_submodule_save()` if you want + * to persist the new value. + * + * @param submodule The submodule to modify + * @param fetch_recurse_submodules Boolean value + * @return old value for fetchRecurseSubmodules + */ +GIT_EXTERN(int) git_submodule_set_fetch_recurse_submodules( + git_submodule *submodule, + int fetch_recurse_submodules); + +/** + * Copy submodule info into ".git/config" file. + * + * Just like "git submodule init", this copies information about the + * submodule into ".git/config". You can use the accessor functions + * above to alter the in-memory git_submodule object and control what + * is written to the config, overriding what is in .gitmodules. + * + * @param submodule The submodule to write into the superproject config + * @param overwrite By default, existing entries will not be overwritten, + * but setting this to true forces them to be updated. + * @return 0 on success, <0 on failure. + */ +GIT_EXTERN(int) git_submodule_init(git_submodule *submodule, int overwrite); + +/** + * Copy submodule remote info into submodule repo. + * + * This copies the information about the submodules URL into the checked out + * submodule config, acting like "git submodule sync". This is useful if + * you have altered the URL for the submodule (or it has been altered by a + * fetch of upstream changes) and you need to update your local repo. + */ +GIT_EXTERN(int) git_submodule_sync(git_submodule *submodule); + +/** + * Open the repository for a submodule. + * + * This is a newly opened repository object. The caller is responsible for + * calling `git_repository_free()` on it when done. Multiple calls to this + * function will return distinct `git_repository` objects. This will only + * work if the submodule is checked out into the working directory. + * + * @param subrepo Pointer to the submodule repo which was opened + * @param submodule Submodule to be opened + * @return 0 on success, <0 if submodule repo could not be opened. + */ +GIT_EXTERN(int) git_submodule_open( + git_repository **repo, + git_submodule *submodule); + +/** + * Reread submodule info from config, index, and HEAD. + * + * Call this to reread cached submodule information for this submodule if + * you have reason to believe that it has changed. + */ +GIT_EXTERN(int) git_submodule_reload(git_submodule *submodule); + +/** + * Reread all submodule info. + * + * Call this to reload all cached submodule information for the repo. + */ +GIT_EXTERN(int) git_submodule_reload_all(git_repository *repo); + +/** + * Get the status for a submodule. + * + * This looks at a submodule and tries to determine the status. It + * will return a combination of the `GIT_SUBMODULE_STATUS` values above. + * How deeply it examines the working directory to do this will depend + * on the `git_submodule_ignore_t` value for the submodule - which can be + * set either temporarily or permanently with `git_submodule_set_ignore()`. + * + * @param status Combination of `GIT_SUBMODULE_STATUS` flags + * @param submodule Submodule for which to get status + * @return 0 on success, <0 on error + */ +GIT_EXTERN(int) git_submodule_status( + unsigned int *status, + git_submodule *submodule); /** @} */ GIT_END_DECL diff --git a/include/git2/tag.h b/include/git2/tag.h index b522451a159..aab4b77a8e2 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -46,7 +46,7 @@ GIT_INLINE(int) git_tag_lookup(git_tag **tag, git_repository *repo, const git_oi * @param len the length of the short identifier * @return 0 or an error code */ -GIT_INLINE(int) git_tag_lookup_prefix(git_tag **tag, git_repository *repo, const git_oid *id, unsigned int len) +GIT_INLINE(int) git_tag_lookup_prefix(git_tag **tag, git_repository *repo, const git_oid *id, size_t len) { return git_object_lookup_prefix((git_object **)tag, repo, id, len, (git_otype)GIT_OBJ_TAG); } diff --git a/include/git2/tree.h b/include/git2/tree.h index f12b15e2e97..e5261417cd2 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -50,7 +50,7 @@ GIT_INLINE(int) git_tree_lookup_prefix( git_tree **tree, git_repository *repo, const git_oid *id, - unsigned int len) + size_t len) { return git_object_lookup_prefix((git_object **)tree, repo, id, len, GIT_OBJ_TREE); } @@ -126,15 +126,26 @@ GIT_EXTERN(const git_tree_entry *) git_tree_entry_byname(git_tree *tree, const c * @param idx the position in the entry list * @return the tree entry; NULL if not found */ -GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, unsigned int idx); +GIT_EXTERN(const git_tree_entry *) git_tree_entry_byindex(git_tree *tree, size_t idx); + +/** + * Lookup a tree entry by SHA value. + * + * Warning: this must examine every entry in the tree, so it is not fast. + * + * @param tree a previously loaded tree. + * @param oid the sha being looked for + * @return the tree entry; NULL if not found + */ +GIT_EXTERN(const git_tree_entry *) git_tree_entry_byoid(git_tree *tree, const git_oid *oid); /** * Get the UNIX file attributes of a tree entry * * @param entry a tree entry - * @return attributes as an integer + * @return filemode as an integer */ -GIT_EXTERN(unsigned int) git_tree_entry_attributes(const git_tree_entry *entry); +GIT_EXTERN(git_filemode_t) git_tree_entry_filemode(const git_tree_entry *entry); /** * Get the filename of a tree entry @@ -252,11 +263,17 @@ GIT_EXTERN(const git_tree_entry *) git_treebuilder_get(git_treebuilder *bld, con * The optional pointer `entry_out` can be used to retrieve a * pointer to the newly created/updated entry. * + * No attempt is being made to ensure that the provided oid points + * to an existing git object in the object database, nor that the + * attributes make sense regarding the type of the pointed at object. + * * @param entry_out Pointer to store the entry (optional) * @param bld Tree builder * @param filename Filename of the entry * @param id SHA1 oid of the entry - * @param attributes Folder attributes of the entry + * @param filemode Folder attributes of the entry. This parameter must + * be valued with one of the following entries: 0040000, 0100644, + * 0100755, 0120000 or 0160000. * @return 0 or an error code */ GIT_EXTERN(int) git_treebuilder_insert( @@ -264,7 +281,7 @@ GIT_EXTERN(int) git_treebuilder_insert( git_treebuilder *bld, const char *filename, const git_oid *id, - unsigned int attributes); + git_filemode_t filemode); /** * Remove an entry from the builder by its filename @@ -340,8 +357,9 @@ enum git_treewalk_mode { * the current (relative) root for the entry and the entry * data itself. * - * If the callback returns a negative value, the passed entry - * will be skipped on the traversal. + * If the callback returns a positive value, the passed entry will be + * skipped on the traversal (in pre mode). A negative value stops the + * walk. * * @param tree The tree to walk * @param callback Function to call on each tree entry diff --git a/include/git2/types.h b/include/git2/types.h index 691903005bc..26e9c57e78b 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -173,12 +173,24 @@ typedef enum { typedef enum { GIT_RESET_SOFT = 1, GIT_RESET_MIXED = 2, + GIT_RESET_HARD = 3, } git_reset_type; +/** Valid modes for index and tree entries. */ +typedef enum { + GIT_FILEMODE_NEW = 0000000, + GIT_FILEMODE_TREE = 0040000, + GIT_FILEMODE_BLOB = 0100644, + GIT_FILEMODE_BLOB_EXECUTABLE = 0100755, + GIT_FILEMODE_LINK = 0120000, + GIT_FILEMODE_COMMIT = 0160000, +} git_filemode_t; + typedef struct git_refspec git_refspec; typedef struct git_remote git_remote; typedef struct git_remote_head git_remote_head; +typedef struct git_remote_callbacks git_remote_callbacks; /** @} */ GIT_END_DECL diff --git a/include/git2/windows.h b/include/git2/windows.h deleted file mode 100644 index 8b743f0aada..00000000000 --- a/include/git2/windows.h +++ /dev/null @@ -1,59 +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_git_windows_h__ -#define INCLUDE_git_windows_h__ - -#include "common.h" - -/** - * @file git2/windows.h - * @brief Windows-specific functions - * @ingroup Git - * @{ - */ -GIT_BEGIN_DECL - -/** - * Set the active codepage for Windows syscalls - * - * All syscalls performed by the library will assume - * this codepage when converting paths and strings - * to use by the Windows kernel. - * - * The default value of UTF-8 will work automatically - * with most Git repositories created on Unix systems. - * - * This settings needs only be changed when working - * with repositories that contain paths in specific, - * non-UTF codepages. - * - * A full list of all available codepage identifiers may - * be found at: - * - * http://msdn.microsoft.com/en-us/library/windows/desktop/dd317756(v=vs.85).aspx - * - * @param codepage numeric codepage identifier - */ -GIT_EXTERN(void) gitwin_set_codepage(unsigned int codepage); - -/** - * Return the active codepage for Windows syscalls - * - * @return numeric codepage identifier - */ -GIT_EXTERN(unsigned int) gitwin_get_codepage(void); - -/** - * Set the active Windows codepage to UTF-8 (this is - * the default value) - */ -GIT_EXTERN(void) gitwin_set_utf8(void); - -/** @} */ -GIT_END_DECL -#endif - diff --git a/libgit2.pc.in b/libgit2.pc.in index 6165ad6789d..ddc03f36b26 100644 --- a/libgit2.pc.in +++ b/libgit2.pc.in @@ -1,4 +1,4 @@ -libdir=@CMAKE_INSTALL_PREFIX@/@INSTALL_LIB@ +libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@ includedir=@CMAKE_INSTALL_PREFIX@/@INSTALL_INC@ Name: libgit2 diff --git a/libgit2_clar.supp b/libgit2_clar.supp new file mode 100644 index 00000000000..f49eb005479 --- /dev/null +++ b/libgit2_clar.supp @@ -0,0 +1,12 @@ +{ + ignore-zlib-errors-cond + Memcheck:Cond + obj:*libz.so* +} + +{ + ignore-giterr-set-leak + Memcheck:Leak + ... + fun:giterr_set +} diff --git a/packaging/rpm/libgit2.spec b/packaging/rpm/libgit2.spec index a6e82b2410a..80e70c1641d 100644 --- a/packaging/rpm/libgit2.spec +++ b/packaging/rpm/libgit2.spec @@ -65,7 +65,7 @@ to compile and develop applications that use libgit2. cmake . \ -DCMAKE_C_FLAGS:STRING="%{optflags}" \ -DCMAKE_INSTALL_PREFIX:PATH=%{_prefix} \ - -DINSTALL_LIB:PATH=%{_libdir} + -DLIB_INSTALL_DIR:PATH=%{_libdir}S make %{?_smp_mflags} %install diff --git a/src/amiga/map.c b/src/amiga/map.c index 2fb065c8b15..c601de7243d 100755 --- a/src/amiga/map.c +++ b/src/amiga/map.c @@ -24,18 +24,15 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs return -1; } - if((out->data = malloc(len))) { - p_lseek(fd, offset, SEEK_SET); - p_read(fd, out->data, len); - } + out->data = malloc(len); + GITERR_CHECK_ALLOC(out->data); - if (!out->data || (out->data == MAP_FAILED)) { - giterr_set(GITERR_OS, "Failed to mmap. Could not write data"); + if (p_lseek(fd, offset, SEEK_SET) < 0 || p_read(fd, out->data, len) != len) + giterr_set(GITERR_OS, "mmap emulation failed"); return -1; } out->len = len; - return 0; } diff --git a/src/attr.c b/src/attr.c index 6fbd005d5b0..68f8d7de636 100644 --- a/src/attr.c +++ b/src/attr.c @@ -1,10 +1,30 @@ #include "repository.h" #include "fileops.h" #include "config.h" +#include "git2/oid.h" #include GIT__USE_STRMAP; +const char *git_attr__true = "[internal]__TRUE__"; +const char *git_attr__false = "[internal]__FALSE__"; +const char *git_attr__unset = "[internal]__UNSET__"; + +git_attr_t git_attr_value(const char *attr) +{ + if (attr == NULL || attr == git_attr__unset) + return GIT_ATTR_UNSPECIFIED_T; + + if (attr == git_attr__true) + return GIT_ATTR_TRUE_T; + + if (attr == git_attr__false) + return GIT_ATTR_FALSE_T; + + return GIT_ATTR_VALUE_T; +} + + static int collect_attr_files( git_repository *repo, uint32_t flags, @@ -22,7 +42,7 @@ int git_attr_get( int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT; - unsigned int i, j; + size_t i, j; git_attr_file *file; git_attr_name attr; git_attr_rule *rule; @@ -74,7 +94,7 @@ int git_attr_get_many( int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT; - unsigned int i, j, k; + size_t i, j, k; git_attr_file *file; git_attr_rule *rule; attr_get_many_info *info = NULL; @@ -138,7 +158,7 @@ int git_attr_foreach( int error; git_attr_path path; git_vector files = GIT_VECTOR_INIT; - unsigned int i, j, k; + size_t i, j, k; git_attr_file *file; git_attr_rule *rule; git_attr_assignment *assign; @@ -163,11 +183,15 @@ int git_attr_foreach( continue; git_strmap_insert(seen, assign->name, assign, error); - if (error >= 0) - error = callback(assign->name, assign->value, payload); + if (error < 0) + goto cleanup; - if (error != 0) + error = callback(assign->name, assign->value, payload); + if (error) { + giterr_clear(); + error = GIT_EUSER; goto cleanup; + } } } } @@ -567,6 +591,18 @@ static int collect_attr_files( return error; } +static char *try_global_default(const char *relpath) +{ + git_buf dflt = GIT_BUF_INIT; + char *rval = NULL; + + if (!git_futils_find_global_file(&dflt, relpath)) + rval = git_buf_detach(&dflt); + + git_buf_free(&dflt); + + return rval; +} int git_attr_cache__init(git_repository *repo) { @@ -584,10 +620,14 @@ int git_attr_cache__init(git_repository *repo) ret = git_config_get_string(&cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG); if (ret < 0 && ret != GIT_ENOTFOUND) return ret; + if (ret == GIT_ENOTFOUND) + cache->cfg_attr_file = try_global_default(GIT_ATTR_CONFIG_DEFAULT); ret = git_config_get_string(&cache->cfg_excl_file, cfg, GIT_IGNORE_CONFIG); if (ret < 0 && ret != GIT_ENOTFOUND) return ret; + if (ret == GIT_ENOTFOUND) + cache->cfg_excl_file = try_global_default(GIT_IGNORE_CONFIG_DEFAULT); giterr_clear(); diff --git a/src/attr.h b/src/attr.h index a35b1160f97..7589bb10a0a 100644 --- a/src/attr.h +++ b/src/attr.h @@ -11,7 +11,9 @@ #include "strmap.h" #define GIT_ATTR_CONFIG "core.attributesfile" +#define GIT_ATTR_CONFIG_DEFAULT ".config/git/attributes" #define GIT_IGNORE_CONFIG "core.excludesfile" +#define GIT_IGNORE_CONFIG_DEFAULT ".config/git/ignore" typedef struct { int initialized; diff --git a/src/attr_file.c b/src/attr_file.c index ca2f8fb5817..b2f312e3e2b 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -5,10 +5,6 @@ #include "git2/tree.h" #include -const char *git_attr__true = "[internal]__TRUE__"; -const char *git_attr__false = "[internal]__FALSE__"; -const char *git_attr__unset = "[internal]__UNSET__"; - static int sort_by_hash_and_name(const void *a_raw, const void *b_raw); static void git_attr_rule__clear(git_attr_rule *rule); @@ -183,7 +179,7 @@ int git_attr_file__lookup_one( const char *attr, const char **value) { - unsigned int i; + size_t i; git_attr_name name; git_attr_rule *rule; @@ -254,18 +250,15 @@ git_attr_assignment *git_attr_rule__lookup_assignment( int git_attr_path__init( git_attr_path *info, const char *path, const char *base) { + ssize_t root; + /* build full path as best we can */ git_buf_init(&info->full, 0); - if (base != NULL && git_path_root(path) < 0) { - if (git_buf_joinpath(&info->full, base, path) < 0) - return -1; - info->path = info->full.ptr + strlen(base); - } else { - if (git_buf_sets(&info->full, path) < 0) - return -1; - info->path = info->full.ptr; - } + if (git_path_join_unrooted(&info->full, path, base, &root) < 0) + return -1; + + info->path = info->full.ptr + root; /* remove trailing slashes */ while (info->full.size > 0) { @@ -426,17 +419,7 @@ int git_attr_fnmatch__parse( return -1; } else { /* strip '\' that might have be used for internal whitespace */ - char *to = spec->pattern; - for (scan = spec->pattern; *scan; to++, scan++) { - if (*scan == '\\') - scan++; /* skip '\' but include next char */ - if (to != scan) - *to = *scan; - } - if (to != scan) { - *to = '\0'; - spec->length = (to - spec->pattern); - } + spec->length = git__unescape(spec->pattern); } return 0; diff --git a/src/attr_file.h b/src/attr_file.h index 7939f838a0c..9d6730d90a5 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -24,6 +24,10 @@ #define GIT_ATTR_FNMATCH_HASWILD (1U << 5) #define GIT_ATTR_FNMATCH_ALLOWSPACE (1U << 6) +extern const char *git_attr__true; +extern const char *git_attr__false; +extern const char *git_attr__unset; + typedef struct { char *pattern; size_t length; diff --git a/src/blob.c b/src/blob.c index 699adec6b7f..6137746e148 100644 --- a/src/blob.c +++ b/src/blob.c @@ -68,6 +68,7 @@ static int write_file_stream( int fd, error; char buffer[4096]; git_odb_stream *stream = NULL; + ssize_t read_len = -1, written = 0; if ((error = git_odb_open_wstream( &stream, odb, (size_t)file_size, GIT_OBJ_BLOB)) < 0) @@ -78,20 +79,18 @@ static int write_file_stream( return -1; } - while (!error && file_size > 0) { - ssize_t read_len = p_read(fd, buffer, sizeof(buffer)); - - if (read_len < 0) { - giterr_set( - GITERR_OS, "Failed to create blob. Can't read whole file"); - error = -1; - } - else if (!(error = stream->write(stream, buffer, read_len))) - file_size -= read_len; + while (!error && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { + error = stream->write(stream, buffer, read_len); + written += read_len; } p_close(fd); + if (written != file_size || read_len < 0) { + giterr_set(GITERR_OS, "Failed to read file into stream"); + error = -1; + } + if (!error) error = stream->finalize_write(oid, stream); @@ -212,8 +211,10 @@ int git_blob_create_fromfile(git_oid *oid, git_repository *repo, const char *pat const char *workdir; int error; + if ((error = git_repository__ensure_not_bare(repo, "create blob from file")) < 0) + return error; + workdir = git_repository_workdir(repo); - assert(workdir); /* error to call this on bare repo */ if (git_buf_joinpath(&full_path, workdir, path) < 0) { git_buf_free(&full_path); diff --git a/src/branch.c b/src/branch.c index 671e4205171..103dfe6212f 100644 --- a/src/branch.c +++ b/src/branch.c @@ -7,8 +7,11 @@ #include "common.h" #include "commit.h" -#include "branch.h" #include "tag.h" +#include "config.h" +#include "refspec.h" + +#include "git2/branch.h" static int retrieve_branch_reference( git_reference **branch_reference_out, @@ -47,76 +50,59 @@ static int create_error_invalid(const char *msg) return -1; } +static int not_a_local_branch(git_reference *ref) +{ + giterr_set(GITERR_INVALID, "Reference '%s' is not a local branch.", git_reference_name(ref)); + return -1; +} + int git_branch_create( - git_oid *oid_out, - git_repository *repo, + git_reference **ref_out, + git_repository *repository, const char *branch_name, const git_object *target, int force) { - git_otype target_type = GIT_OBJ_BAD; git_object *commit = NULL; git_reference *branch = NULL; git_buf canonical_branch_name = GIT_BUF_INIT; int error = -1; - assert(repo && branch_name && target && oid_out); - - if (git_object_owner(target) != repo) - return create_error_invalid("The given target does not belong to this repository"); - - target_type = git_object_type(target); + assert(branch_name && target && ref_out); + assert(git_object_owner(target) == repository); - switch (target_type) - { - case GIT_OBJ_TAG: - if (git_tag_peel(&commit, (git_tag *)target) < 0) - goto cleanup; - - if (git_object_type(commit) != GIT_OBJ_COMMIT) { - create_error_invalid("The given target does not resolve to a commit"); - goto cleanup; - } - break; - - case GIT_OBJ_COMMIT: - commit = (git_object *)target; - break; - - default: - return create_error_invalid("Only git_tag and git_commit objects are valid targets."); - } + if (git_object_peel(&commit, (git_object *)target, GIT_OBJ_COMMIT) < 0) + return create_error_invalid("The given target does not resolve to a commit"); if (git_buf_joinpath(&canonical_branch_name, GIT_REFS_HEADS_DIR, branch_name) < 0) goto cleanup; - if (git_reference_create_oid(&branch, repo, git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) + if (git_reference_create_oid(&branch, repository, + git_buf_cstr(&canonical_branch_name), git_object_id(commit), force) < 0) goto cleanup; - git_oid_cpy(oid_out, git_reference_oid(branch)); + *ref_out = branch; error = 0; cleanup: - if (target_type == GIT_OBJ_TAG) - git_object_free(commit); - - git_reference_free(branch); + git_object_free(commit); git_buf_free(&canonical_branch_name); return error; } -int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_t branch_type) +int git_branch_delete(git_reference *branch) { - git_reference *branch = NULL; git_reference *head = NULL; - int error; - assert((branch_type == GIT_BRANCH_LOCAL) || (branch_type == GIT_BRANCH_REMOTE)); + assert(branch); - if ((error = retrieve_branch_reference(&branch, repo, branch_name, branch_type == GIT_BRANCH_REMOTE)) < 0) - return error; + if (!git_reference_is_branch(branch) && + !git_reference_is_remote(branch)) { + giterr_set(GITERR_INVALID, "Reference '%s' is not a valid branch.", git_reference_name(branch)); + return -1; + } - if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { + if (git_reference_lookup(&head, git_reference_owner(branch), GIT_HEAD_FILE) < 0) { giterr_set(GITERR_REFERENCE, "Cannot locate HEAD."); goto on_error; } @@ -124,7 +110,7 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_ if ((git_reference_type(head) == GIT_REF_SYMBOLIC) && (strcmp(git_reference_target(head), git_reference_name(branch)) == 0)) { giterr_set(GITERR_REFERENCE, - "Cannot delete branch '%s' as it is the current HEAD of the repository.", branch_name); + "Cannot delete branch '%s' as it is the current HEAD of the repository.", git_reference_name(branch)); goto on_error; } @@ -136,7 +122,6 @@ int git_branch_delete(git_repository *repo, const char *branch_name, git_branch_ on_error: git_reference_free(head); - git_reference_free(branch); return -1; } @@ -183,28 +168,106 @@ int git_branch_foreach( return git_reference_foreach(repo, GIT_REF_LISTALL, &branch_foreach_cb, (void *)&filter); } -int git_branch_move(git_repository *repo, const char *old_branch_name, const char *new_branch_name, int force) +int git_branch_move( + git_reference *branch, + const char *new_branch_name, + int force) { - git_reference *reference = NULL; - git_buf old_reference_name = GIT_BUF_INIT, new_reference_name = GIT_BUF_INIT; - int error = 0; + git_buf new_reference_name = GIT_BUF_INIT; + int error; - if ((error = git_buf_joinpath(&old_reference_name, GIT_REFS_HEADS_DIR, old_branch_name)) < 0) - goto cleanup; + assert(branch && new_branch_name); - /* We need to be able to return GIT_ENOTFOUND */ - if ((error = git_reference_lookup(&reference, repo, git_buf_cstr(&old_reference_name))) < 0) - goto cleanup; + if (!git_reference_is_branch(branch)) + return not_a_local_branch(branch); if ((error = git_buf_joinpath(&new_reference_name, GIT_REFS_HEADS_DIR, new_branch_name)) < 0) goto cleanup; - error = git_reference_rename(reference, git_buf_cstr(&new_reference_name), force); + error = git_reference_rename(branch, git_buf_cstr(&new_reference_name), force); cleanup: - git_reference_free(reference); - git_buf_free(&old_reference_name); git_buf_free(&new_reference_name); return error; } + +int git_branch_lookup( + git_reference **ref_out, + git_repository *repo, + const char *branch_name, + git_branch_t branch_type) +{ + assert(ref_out && repo && branch_name); + + return retrieve_branch_reference(ref_out, repo, branch_name, branch_type == GIT_BRANCH_REMOTE); +} + +static int retrieve_tracking_configuration( + const char **out, git_reference *branch, const char *format) +{ + git_config *config; + git_buf buf = GIT_BUF_INIT; + int error; + + if (git_repository_config__weakptr(&config, git_reference_owner(branch)) < 0) + return -1; + + if (git_buf_printf(&buf, format, + git_reference_name(branch) + strlen(GIT_REFS_HEADS_DIR)) < 0) + return -1; + + error = git_config_get_string(out, config, git_buf_cstr(&buf)); + git_buf_free(&buf); + return error; +} + +int git_branch_tracking( + git_reference **tracking_out, + git_reference *branch) +{ + const char *remote_name, *merge_name; + git_buf buf = GIT_BUF_INIT; + int error = -1; + git_remote *remote = NULL; + const git_refspec *refspec; + + assert(tracking_out && branch); + + if (!git_reference_is_branch(branch)) + return not_a_local_branch(branch); + + if ((error = retrieve_tracking_configuration(&remote_name, branch, "branch.%s.remote")) < 0) + goto cleanup; + + if ((error = retrieve_tracking_configuration(&merge_name, branch, "branch.%s.merge")) < 0) + goto cleanup; + + if (strcmp(".", remote_name) != 0) { + if ((error = git_remote_load(&remote, git_reference_owner(branch), remote_name)) < 0) + goto cleanup; + + refspec = git_remote_fetchspec(remote); + if (refspec == NULL + || refspec->src == NULL + || refspec->dst == NULL) { + error = GIT_ENOTFOUND; + goto cleanup; + } + + if (git_refspec_transform_r(&buf, refspec, merge_name) < 0) + goto cleanup; + } else + if (git_buf_sets(&buf, merge_name) < 0) + goto cleanup; + + error = git_reference_lookup( + tracking_out, + git_reference_owner(branch), + git_buf_cstr(&buf)); + +cleanup: + git_remote_free(remote); + git_buf_free(&buf); + return error; +} diff --git a/src/branch.h b/src/branch.h deleted file mode 100644 index d0e5abc8b02..00000000000 --- a/src/branch.h +++ /dev/null @@ -1,17 +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_branch_h__ -#define INCLUDE_branch_h__ - -#include "git2/branch.h" - -struct git_branch { - char *remote; /* TODO: Make this a git_remote */ - char *merge; -}; - -#endif diff --git a/src/buffer.c b/src/buffer.c index 04aaec3df15..61cfaf9e240 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -141,6 +141,51 @@ int git_buf_puts(git_buf *buf, const char *string) return git_buf_put(buf, string, strlen(string)); } +int git_buf_puts_escaped( + git_buf *buf, const char *string, const char *esc_chars, const char *esc_with) +{ + const char *scan; + size_t total = 0, esc_len = strlen(esc_with), count; + + if (!string) + return 0; + + for (scan = string; *scan; ) { + /* count run of non-escaped characters */ + count = strcspn(scan, esc_chars); + total += count; + scan += count; + /* count run of escaped characters */ + count = strspn(scan, esc_chars); + total += count * (esc_len + 1); + scan += count; + } + + ENSURE_SIZE(buf, buf->size + total + 1); + + for (scan = string; *scan; ) { + count = strcspn(scan, esc_chars); + + memmove(buf->ptr + buf->size, scan, count); + scan += count; + buf->size += count; + + for (count = strspn(scan, esc_chars); count > 0; --count) { + /* copy escape sequence */ + memmove(buf->ptr + buf->size, esc_with, esc_len); + buf->size += esc_len; + /* copy character to be escaped */ + buf->ptr[buf->size] = *scan; + buf->size++; + scan++; + } + } + + buf->ptr[buf->size] = '\0'; + + return 0; +} + int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) { int len; @@ -460,3 +505,7 @@ bool git_buf_is_binary(const git_buf *buf) return ((printable >> 7) < nonprintable); } +void git_buf_unescape(git_buf *buf) +{ + buf->size = git__unescape(buf->ptr); +} diff --git a/src/buffer.h b/src/buffer.h index 50c75f64e1b..17922e408ea 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -90,6 +90,18 @@ void git_buf_rtruncate_at_char(git_buf *path, char separator); int git_buf_join_n(git_buf *buf, char separator, int nbuf, ...); int git_buf_join(git_buf *buf, char separator, const char *str_a, const char *str_b); +/** + * Copy string into buf prefixing every character that is contained in the + * esc_chars string with the esc_with string. + */ +int git_buf_puts_escaped( + git_buf *buf, const char *string, const char *esc_chars, const char *esc_with); + +GIT_INLINE(int) git_buf_puts_escape_regex(git_buf *buf, const char *string) +{ + return git_buf_puts_escaped(buf, string, "^.[]$()|*+?{}\\", "\\"); +} + /** * Join two strings as paths, inserting a slash between as needed. * @return 0 on success, -1 on failure @@ -121,6 +133,13 @@ GIT_INLINE(ssize_t) git_buf_rfind_next(git_buf *buf, char ch) return idx; } +GIT_INLINE(ssize_t) git_buf_rfind(git_buf *buf, char ch) +{ + ssize_t idx = (ssize_t)buf->size - 1; + while (idx >= 0 && buf->ptr[idx] != ch) idx--; + return idx; +} + /* Remove whitespace from the end of the buffer */ void git_buf_rtrim(git_buf *buf); @@ -132,4 +151,7 @@ int git_buf_common_prefix(git_buf *buf, const git_strarray *strings); /* Check if buffer looks like it contains binary data */ bool git_buf_is_binary(const git_buf *buf); +/* Unescape all characters in a buffer */ +void git_buf_unescape(git_buf *buf); + #endif diff --git a/src/cache.c b/src/cache.c index f8d89403be3..1f5b8872c01 100644 --- a/src/cache.c +++ b/src/cache.c @@ -11,6 +11,7 @@ #include "thread-utils.h" #include "util.h" #include "cache.h" +#include "git2/oid.h" int git_cache_init(git_cache *cache, size_t size, git_cached_obj_freeptr free_ptr) { @@ -88,12 +89,13 @@ void *git_cache_try_store(git_cache *cache, void *_entry) git_cached_obj_decref(node, cache->free_obj); cache->nodes[hash & cache->size_mask] = entry; } + + /* increase the refcount again, because we are + * returning it to the user */ + git_cached_obj_incref(entry); + } git_mutex_unlock(&cache->lock); - /* increase the refcount again, because we are - * returning it to the user */ - git_cached_obj_incref(entry); - return entry; } diff --git a/src/checkout.c b/src/checkout.c new file mode 100644 index 00000000000..7cf9fe03366 --- /dev/null +++ b/src/checkout.c @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2/checkout.h" +#include "git2/repository.h" +#include "git2/refs.h" +#include "git2/tree.h" +#include "git2/blob.h" +#include "git2/config.h" +#include "git2/diff.h" + +#include "common.h" +#include "refs.h" +#include "buffer.h" +#include "repository.h" +#include "filter.h" +#include "blob.h" + +struct checkout_diff_data +{ + git_buf *path; + size_t workdir_len; + git_checkout_opts *checkout_opts; + git_indexer_stats *stats; + git_repository *owner; + bool can_symlink; +}; + +static int buffer_to_file( + git_buf *buffer, + const char *path, + mode_t dir_mode, + int file_open_flags, + mode_t file_mode) +{ + int fd, error_write, error_close; + + if (git_futils_mkpath2file(path, dir_mode) < 0) + return -1; + + if ((fd = p_open(path, file_open_flags, file_mode)) < 0) + return -1; + + error_write = p_write(fd, git_buf_cstr(buffer), git_buf_len(buffer)); + error_close = p_close(fd); + + return error_write ? error_write : error_close; +} + +static int blob_content_to_file( + git_blob *blob, + const char *path, + mode_t entry_filemode, + git_checkout_opts *opts) +{ + int error = -1, nb_filters = 0; + mode_t file_mode = opts->file_mode; + bool dont_free_filtered = false; + git_buf unfiltered = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + git_vector filters = GIT_VECTOR_INIT; + + if (opts->disable_filters || + (nb_filters = git_filters_load( + &filters, + git_object_owner((git_object *)blob), + path, + GIT_FILTER_TO_WORKTREE)) == 0) { + + /* Create a fake git_buf from the blob raw data... */ + filtered.ptr = blob->odb_object->raw.data; + filtered.size = blob->odb_object->raw.len; + + /* ... and make sure it doesn't get unexpectedly freed */ + dont_free_filtered = true; + } + + if (nb_filters < 0) + return nb_filters; + + if (nb_filters > 0) { + if (git_blob__getbuf(&unfiltered, blob) < 0) + goto cleanup; + + if ((error = git_filters_apply(&filtered, &unfiltered, &filters)) < 0) + goto cleanup; + } + + /* Allow overriding of file mode */ + if (!file_mode) + file_mode = entry_filemode; + + error = buffer_to_file(&filtered, path, opts->dir_mode, opts->file_open_flags, file_mode); + +cleanup: + git_filters_free(&filters); + git_buf_free(&unfiltered); + if (!dont_free_filtered) + git_buf_free(&filtered); + + return error; +} + +static int blob_content_to_link(git_blob *blob, const char *path, bool can_symlink) +{ + git_buf linktarget = GIT_BUF_INIT; + int error; + + if (git_blob__getbuf(&linktarget, blob) < 0) + return -1; + + if (can_symlink) + error = p_symlink(git_buf_cstr(&linktarget), path); + else + error = git_futils_fake_symlink(git_buf_cstr(&linktarget), path); + + git_buf_free(&linktarget); + + return error; +} + +static int checkout_blob( + git_repository *repo, + git_oid *blob_oid, + const char *path, + mode_t filemode, + bool can_symlink, + git_checkout_opts *opts) +{ + git_blob *blob; + int error; + + if (git_blob_lookup(&blob, repo, blob_oid) < 0) + return -1; /* Add an error message */ + + if (S_ISLNK(filemode)) + error = blob_content_to_link(blob, path, can_symlink); + else + error = blob_content_to_file(blob, path, filemode, opts); + + git_blob_free(blob); + + return error; +} + +static int checkout_diff_fn( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + struct checkout_diff_data *data; + int error = -1; + git_checkout_opts *opts; + + data = (struct checkout_diff_data *)cb_data; + + data->stats->processed = (unsigned int)(data->stats->total * progress); + + git_buf_truncate(data->path, data->workdir_len); + if (git_buf_joinpath(data->path, git_buf_cstr(data->path), delta->new_file.path) < 0) + return -1; + + opts = data->checkout_opts; + + switch (delta->status) { + case GIT_DELTA_UNTRACKED: + if (!(opts->checkout_strategy & GIT_CHECKOUT_REMOVE_UNTRACKED)) + return 0; + + if (!git__suffixcmp(delta->new_file.path, "/")) + error = git_futils_rmdir_r(git_buf_cstr(data->path), GIT_DIRREMOVAL_FILES_AND_DIRS); + else + error = p_unlink(git_buf_cstr(data->path)); + break; + + case GIT_DELTA_MODIFIED: + if (!(opts->checkout_strategy & GIT_CHECKOUT_OVERWRITE_MODIFIED)) { + + if ((opts->skipped_notify_cb != NULL) + && (opts->skipped_notify_cb( + delta->new_file.path, + &delta->old_file.oid, + delta->old_file.mode, + opts->notify_payload))) { + giterr_clear(); + return GIT_EUSER; + } + + return 0; + } + + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + opts) < 0) + goto cleanup; + + break; + + case GIT_DELTA_DELETED: + if (!(opts->checkout_strategy & GIT_CHECKOUT_CREATE_MISSING)) + return 0; + + if (checkout_blob( + data->owner, + &delta->old_file.oid, + git_buf_cstr(data->path), + delta->old_file.mode, + data->can_symlink, + opts) < 0) + goto cleanup; + + break; + + default: + giterr_set(GITERR_INVALID, "Unexpected status (%d) for path '%s'.", delta->status, delta->new_file.path); + goto cleanup; + } + + error = 0; + +cleanup: + return error; +} + +static int retrieve_symlink_capabilities(git_repository *repo, bool *can_symlink) +{ + git_config *cfg; + int error; + + if (git_repository_config__weakptr(&cfg, repo) < 0) + return -1; + + error = git_config_get_bool((int *)can_symlink, cfg, "core.symlinks"); + + /* + * 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; + } + + return error; +} + +static void normalize_options(git_checkout_opts *normalized, git_checkout_opts *proposed) +{ + assert(normalized); + + if (!proposed) + memset(normalized, 0, sizeof(git_checkout_opts)); + else + memmove(normalized, proposed, sizeof(git_checkout_opts)); + + /* Default options */ + if (!normalized->checkout_strategy) + normalized->checkout_strategy = GIT_CHECKOUT_DEFAULT; + + /* 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; +} + +int git_checkout_index( + git_repository *repo, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + git_index *index = NULL; + 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; + git_buf workdir = GIT_BUF_INIT; + + int error; + + assert(repo); + + if ((git_repository__ensure_not_bare(repo, "checkout")) < 0) + return GIT_EBAREREPO; + + diff_opts.flags = GIT_DIFF_INCLUDE_UNTRACKED; + + if (opts && opts->paths.count > 0) + diff_opts.pathspec = opts->paths; + + if ((error = git_diff_workdir_to_index(repo, &diff_opts, &diff)) < 0) + goto cleanup; + + if ((error = git_buf_puts(&workdir, git_repository_workdir(repo))) < 0) + goto cleanup; + + normalize_options(&checkout_opts, opts); + + if (!stats) + stats = &dummy_stats; + + stats->processed = 0; + + if ((git_repository_index(&index, repo)) < 0) + goto cleanup; + + stats->total = git_index_entrycount(index); + + 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; + + error = git_diff_foreach(diff, &data, checkout_diff_fn, NULL, NULL); + +cleanup: + git_index_free(index); + git_diff_list_free(diff); + git_buf_free(&workdir); + return error; +} + +int git_checkout_tree( + git_repository *repo, + git_object *treeish, + git_checkout_opts *opts, + git_indexer_stats *stats) +{ + 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; + } + + if ((error = git_repository_index(&index, repo)) < 0) + goto cleanup; + + if ((error = git_index_read_tree(index, tree, NULL)) < 0) + goto cleanup; + + 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) +{ + int error; + git_tree *tree = NULL; + + assert(repo); + + if (git_repository_head_tree(&tree, repo) < 0) + return -1; + + error = git_checkout_tree(repo, (git_object *)tree, opts, stats); + + git_tree_free(tree); + + return error; +} diff --git a/src/clone.c b/src/clone.c new file mode 100644 index 00000000000..80a13d0f243 --- /dev/null +++ b/src/clone.c @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2/clone.h" +#include "git2/remote.h" +#include "git2/revparse.h" +#include "git2/branch.h" +#include "git2/config.h" +#include "git2/checkout.h" +#include "git2/commit.h" +#include "git2/tree.h" + +#include "common.h" +#include "remote.h" +#include "fileops.h" +#include "refs.h" +#include "path.h" + +struct HeadInfo { + git_repository *repo; + git_oid remote_head_oid; + git_buf branchname; +}; + +static int create_tracking_branch(git_repository *repo, const git_oid *target, const char *name) +{ + git_object *head_obj = NULL; + git_reference *branch_ref; + int retcode = GIT_ERROR; + + /* Find the target commit */ + if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0) + return GIT_ERROR; + + /* Create the new branch */ + if (!git_branch_create(&branch_ref, repo, name, head_obj, 0)) { + git_config *cfg; + + git_reference_free(branch_ref); + /* Set up tracking */ + if (!git_repository_config(&cfg, repo)) { + git_buf remote = GIT_BUF_INIT; + git_buf merge = GIT_BUF_INIT; + git_buf merge_target = GIT_BUF_INIT; + if (!git_buf_printf(&remote, "branch.%s.remote", name) && + !git_buf_printf(&merge, "branch.%s.merge", name) && + !git_buf_printf(&merge_target, "refs/heads/%s", name) && + !git_config_set_string(cfg, git_buf_cstr(&remote), "origin") && + !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { + retcode = 0; + } + git_buf_free(&remote); + git_buf_free(&merge); + git_buf_free(&merge_target); + git_config_free(cfg); + } + } + + git_object_free(head_obj); + return retcode; +} + +static int reference_matches_remote_head(const char *head_name, void *payload) +{ + struct HeadInfo *head_info = (struct HeadInfo *)payload; + git_oid oid; + + /* Stop looking if we've already found a match */ + if (git_buf_len(&head_info->branchname) > 0) return 0; + + if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) && + !git_oid_cmp(&head_info->remote_head_oid, &oid)) { + git_buf_puts(&head_info->branchname, + head_name+strlen("refs/remotes/origin/")); + } + return 0; +} + +static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name) +{ + int retcode = GIT_ERROR; + + if (!create_tracking_branch(repo, target, name)) { + git_reference *head; + if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) { + git_buf targetbuf = GIT_BUF_INIT; + if (!git_buf_printf(&targetbuf, "refs/heads/%s", name)) { + retcode = git_reference_set_target(head, git_buf_cstr(&targetbuf)); + } + git_buf_free(&targetbuf); + git_reference_free(head); + } + } + + return retcode; +} + +static int update_head_to_remote(git_repository *repo, git_remote *remote) +{ + int retcode = GIT_ERROR; + git_remote_head *remote_head; + git_oid oid; + struct HeadInfo head_info; + + /* Get the remote's HEAD. This is always the first ref in remote->refs. */ + remote_head = remote->refs.contents[0]; + git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); + git_buf_init(&head_info.branchname, 16); + head_info.repo = repo; + + /* Check to see if "master" matches the remote head */ + if (!git_reference_name_to_oid(&oid, repo, "refs/remotes/origin/master") && + !git_oid_cmp(&remote_head->oid, &oid)) { + retcode = update_head_to_new_branch(repo, &oid, "master"); + } + /* Not master. Check all the other refs. */ + else if (!git_reference_foreach(repo, GIT_REF_LISTALL, + reference_matches_remote_head, + &head_info) && + git_buf_len(&head_info.branchname) > 0) { + retcode = update_head_to_new_branch(repo, &head_info.remote_head_oid, + git_buf_cstr(&head_info.branchname)); + } + + git_buf_free(&head_info.branchname); + return retcode; +} + +/* + * submodules? + */ + + + +static int setup_remotes_and_fetch(git_repository *repo, + const char *origin_url, + git_indexer_stats *fetch_stats) +{ + 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, "origin", origin_url)) { + /* Connect and download everything */ + if (!git_remote_connect(origin, GIT_DIR_FETCH)) { + if (!git_remote_download(origin, &bytes, fetch_stats)) { + /* 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 */ + if (!update_head_to_remote(repo, origin)) { + retcode = 0; + } + } + } + git_remote_disconnect(origin); + } + git_remote_free(origin); + } + + return retcode; +} + + +static bool path_is_okay(const char *path) +{ + /* The path must either not exist, or be an empty directory */ + if (!git_path_exists(path)) return true; + if (!git_path_is_empty_dir(path)) { + giterr_set(GITERR_INVALID, + "'%s' exists and is not an empty directory", path); + return false; + } + return true; +} + + +static int clone_internal(git_repository **out, + const char *origin_url, + const char *path, + git_indexer_stats *fetch_stats, + int 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) { + /* Failed to fetch; clean up */ + git_repository_free(repo); + git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS); + } else { + *out = repo; + retcode = 0; + } + } + + return retcode; +} + +int git_clone_bare(git_repository **out, + const char *origin_url, + const char *dest_path, + git_indexer_stats *fetch_stats) +{ + assert(out && origin_url && dest_path); + return clone_internal(out, origin_url, dest_path, fetch_stats, 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 retcode = GIT_ERROR; + + assert(out && origin_url && workdir_path); + + if (!(retcode = clone_internal(out, origin_url, workdir_path, fetch_stats, 0))) + retcode = git_checkout_head(*out, checkout_opts, checkout_stats); + + return retcode; +} diff --git a/src/commit.c b/src/commit.c index a3baf9d4e22..b66978aff32 100644 --- a/src/commit.c +++ b/src/commit.c @@ -226,22 +226,28 @@ GIT_COMMIT_GETTER(const char *, message, commit->message) GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) -GIT_COMMIT_GETTER(unsigned int, parentcount, commit->parent_oids.length) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_oids.length) GIT_COMMIT_GETTER(const git_oid *, tree_oid, &commit->tree_oid); - int git_commit_tree(git_tree **tree_out, git_commit *commit) { assert(commit); return git_tree_lookup(tree_out, commit->object.repo, &commit->tree_oid); } +const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n) +{ + assert(commit); + + return git_vector_get(&commit->parent_oids, n); +} + int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) { - git_oid *parent_oid; + const git_oid *parent_oid; assert(commit); - parent_oid = git_vector_get(&commit->parent_oids, n); + parent_oid = git_commit_parent_oid(commit, n); if (parent_oid == NULL) { giterr_set(GITERR_INVALID, "Parent %u does not exist", n); return GIT_ENOTFOUND; @@ -250,9 +256,36 @@ int git_commit_parent(git_commit **parent, git_commit *commit, unsigned int n) return git_commit_lookup(parent, commit->object.repo, parent_oid); } -const git_oid *git_commit_parent_oid(git_commit *commit, unsigned int n) +int git_commit_nth_gen_ancestor( + git_commit **ancestor, + const git_commit *commit, + unsigned int n) { - assert(commit); + git_commit *current, *parent; + int error; - return git_vector_get(&commit->parent_oids, n); + assert(ancestor && commit); + + current = (git_commit *)commit; + + if (n == 0) + return git_commit_lookup( + ancestor, + commit->object.repo, + git_object_id((const git_object *)commit)); + + while (n--) { + error = git_commit_parent(&parent, (git_commit *)current, 0); + + if (current != commit) + git_commit_free(current); + + if (error < 0) + return error; + + current = parent; + } + + *ancestor = parent; + return 0; } diff --git a/src/common.h b/src/common.h index 419a8d06b8b..1d85428b3c4 100644 --- a/src/common.h +++ b/src/common.h @@ -49,15 +49,6 @@ #include -extern void git___throw(const char *, ...) GIT_FORMAT_PRINTF(1, 2); -#define git__throw(error, ...) \ - (git___throw(__VA_ARGS__), error) - -extern void git___rethrow(const char *, ...) GIT_FORMAT_PRINTF(1, 2); -#define git__rethrow(error, ...) \ - (git___rethrow(__VA_ARGS__), error) - - #define GITERR_CHECK_ALLOC(ptr) if (ptr == NULL) { return -1; } void giterr_set_oom(void); @@ -68,5 +59,7 @@ 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 d18b85c30a4..e62dccf51ad 100644 --- a/src/config.c +++ b/src/config.c @@ -136,17 +136,27 @@ int git_config_add_file(git_config *cfg, git_config_file *file, int priority) * Loop over all the variables */ -int git_config_foreach(git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +int git_config_foreach( + git_config *cfg, int (*fn)(const char *, const char *, void *), void *data) +{ + return git_config_foreach_match(cfg, NULL, fn, data); +} + +int git_config_foreach_match( + git_config *cfg, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { int ret = 0; unsigned int i; file_internal *internal; git_config_file *file; - for(i = 0; i < cfg->files.length && ret == 0; ++i) { + for (i = 0; i < cfg->files.length && ret == 0; ++i) { internal = git_vector_get(&cfg->files, i); file = internal->file; - ret = file->foreach(file, fn, data); + ret = file->foreach(file, regexp, fn, data); } return ret; @@ -400,7 +410,7 @@ int git_config_get_multivar(git_config *cfg, const char *name, const char *regex file_internal *internal; git_config_file *file; int ret = GIT_ENOTFOUND; - unsigned int i; + size_t i; assert(cfg->files.length); @@ -424,7 +434,7 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex file_internal *internal; git_config_file *file; int ret = GIT_ENOTFOUND; - unsigned int i; + size_t i; for (i = cfg->files.length; i > 0; --i) { internal = git_vector_get(&cfg->files, i - 1); @@ -439,7 +449,12 @@ int git_config_set_multivar(git_config *cfg, const char *name, const char *regex int git_config_find_global_r(git_buf *path) { - return git_futils_find_global_file(path, GIT_CONFIG_FILENAME); + int error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME); + + if (error == GIT_ENOTFOUND) + error = git_futils_find_global_file(path, GIT_CONFIG_FILENAME_ALT); + + return error; } int git_config_find_global(char *global_config_path, size_t length) @@ -491,17 +506,28 @@ int git_config_find_system(char *system_config_path, size_t length) return 0; } -int git_config_open_global(git_config **out) +int git_config_open_default(git_config **out) { int error; - git_buf path = GIT_BUF_INIT; + git_config *cfg = NULL; + git_buf buf = GIT_BUF_INIT; - if ((error = git_config_find_global_r(&path)) < 0) - return error; + error = git_config_new(&cfg); - error = git_config_open_ondisk(out, git_buf_cstr(&path)); - git_buf_free(&path); + if (!error && !git_config_find_global_r(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, 2); + + if (!error && !git_config_find_system_r(&buf)) + error = git_config_add_file_ondisk(cfg, buf.ptr, 1); + + git_buf_free(&buf); + + if (error && cfg) { + git_config_free(cfg); + cfg = NULL; + } + + *out = cfg; return error; } - diff --git a/src/config.h b/src/config.h index 82e98ce51ba..5475ef384c8 100644 --- a/src/config.h +++ b/src/config.h @@ -13,6 +13,7 @@ #include "repository.h" #define GIT_CONFIG_FILENAME ".gitconfig" +#define GIT_CONFIG_FILENAME_ALT ".config/git/config" #define GIT_CONFIG_FILENAME_INREPO "config" #define GIT_CONFIG_FILENAME_SYSTEM "gitconfig" #define GIT_CONFIG_FILE_MODE 0666 diff --git a/src/config_file.c b/src/config_file.c index fd1aa8d0863..4ba83d1d98e 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -188,25 +188,51 @@ static void backend_free(git_config_file *_backend) git__free(backend); } -static int file_foreach(git_config_file *backend, int (*fn)(const char *, const char *, void *), void *data) +static int file_foreach( + git_config_file *backend, + const char *regexp, + int (*fn)(const char *, const char *, void *), + void *data) { diskfile_backend *b = (diskfile_backend *)backend; - cvar_t *var; + cvar_t *var, *next_var; const char *key; + regex_t regex; + int result = 0; if (!b->values) return 0; + if (regexp != NULL) { + if ((result = regcomp(®ex, regexp, REG_EXTENDED)) < 0) { + giterr_set_regex(®ex, result); + regfree(®ex); + return -1; + } + } + git_strmap_foreach(b->values, key, var, - do { - if (fn(key, var->value, data) < 0) - break; + for (; var != NULL; var = next_var) { + next_var = CVAR_LIST_NEXT(var); - var = CVAR_LIST_NEXT(var); - } while (var != NULL); + /* skip non-matching keys if regexp was provided */ + if (regexp && regexec(®ex, key, 0, NULL, 0) != 0) + continue; + + /* abort iterator on non-zero return value */ + if (fn(key, var->value, data)) { + giterr_clear(); + result = GIT_EUSER; + goto cleanup; + } + } ); - return 0; +cleanup: + if (regexp != NULL) + regfree(®ex); + + return result; } static int config_set(git_config_file *cfg, const char *name, const char *value) @@ -230,11 +256,17 @@ static int config_set(git_config_file *cfg, const char *name, const char *value) char *tmp = NULL; git__free(key); + if (existing->next != NULL) { giterr_set(GITERR_CONFIG, "Multivar incompatible with simple set"); return -1; } + /* don't update if old and new values already match */ + if ((!existing->value && !value) || + (existing->value && value && !strcmp(existing->value, value))) + return 0; + if (value) { tmp = git__strdup(value); GITERR_CHECK_ALLOC(tmp); @@ -337,6 +369,7 @@ static int config_get_multivar( result = regcomp(®ex, regex_str, REG_EXTENDED); if (result < 0) { giterr_set_regex(®ex, result); + regfree(®ex); return -1; } @@ -396,6 +429,7 @@ static int config_set_multivar( if (result < 0) { git__free(key); giterr_set_regex(&preg, result); + regfree(&preg); return -1; } @@ -786,7 +820,7 @@ static int parse_section_header(diskfile_backend *cfg, char **section_out) static int skip_bom(diskfile_backend *cfg) { - static const char utf8_bom[] = "\xef\xbb\xbf"; + static const char utf8_bom[] = { '\xef', '\xbb', '\xbf' }; if (cfg->reader.buffer.size < sizeof(utf8_bom)) return 0; @@ -960,9 +994,12 @@ static int write_section(git_filebuf *file, const char *key) if (dot == NULL) { git_buf_puts(&buf, key); } else { + char *escaped; git_buf_put(&buf, key, dot - key); - /* TODO: escape */ - git_buf_printf(&buf, " \"%s\"", dot + 1); + escaped = escape_value(dot + 1); + GITERR_CHECK_ALLOC(escaped); + git_buf_printf(&buf, " \"%s\"", escaped); + git__free(escaped); } git_buf_puts(&buf, "]\n"); @@ -1315,10 +1352,8 @@ static int parse_variable(diskfile_backend *cfg, char **var_name, char **var_val else value_start = var_end + 1; - if (git__isspace(var_end[-1])) { - do var_end--; - while (git__isspace(var_end[0])); - } + do var_end--; + while (git__isspace(*var_end)); *var_name = git__strndup(line, var_end - line + 1); GITERR_CHECK_ALLOC(*var_name); diff --git a/src/config_file.h b/src/config_file.h index 0080b571335..bf687b51695 100644 --- a/src/config_file.h +++ b/src/config_file.h @@ -19,12 +19,39 @@ GIT_INLINE(void) git_config_file_free(git_config_file *cfg) cfg->free(cfg); } +GIT_INLINE(int) git_config_file_get_string( + const char **out, git_config_file *cfg, const char *name) +{ + return cfg->get(cfg, name, out); +} + +GIT_INLINE(int) git_config_file_set_string( + git_config_file *cfg, const char *name, const char *value) +{ + return cfg->set(cfg, name, value); +} + +GIT_INLINE(int) git_config_file_delete( + git_config_file *cfg, const char *name) +{ + return cfg->del(cfg, name); +} + GIT_INLINE(int) git_config_file_foreach( git_config_file *cfg, int (*fn)(const char *key, const char *value, void *data), void *data) { - return cfg->foreach(cfg, fn, data); + return cfg->foreach(cfg, NULL, fn, data); +} + +GIT_INLINE(int) git_config_file_foreach_match( + git_config_file *cfg, + const char *regexp, + int (*fn)(const char *key, const char *value, void *data), + void *data) +{ + return cfg->foreach(cfg, regexp, fn, data); } #endif diff --git a/src/crlf.c b/src/crlf.c index 303a46d3bfd..5e86b4eb66d 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -184,7 +184,88 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou return drop_crlf(dest, source); } -int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending) +{ + const char *scan = git_buf_cstr(source), + *next, + *scan_end = git_buf_cstr(source) + git_buf_len(source); + + while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) { + if (next > scan) + git_buf_put(dest, scan, next-scan); + git_buf_puts(dest, ending); + scan = next + 1; + } + + git_buf_put(dest, scan, scan_end - scan); + return 0; +} + +static const char *line_ending(struct crlf_filter *filter) +{ + switch (filter->attrs.crlf_action) { + case GIT_CRLF_BINARY: + case GIT_CRLF_INPUT: + return "\n"; + + case GIT_CRLF_CRLF: + return "\r\n"; + + case GIT_CRLF_AUTO: + case GIT_CRLF_TEXT: + case GIT_CRLF_GUESS: + break; + + default: + goto line_ending_error; + } + + switch (filter->attrs.eol) { + case GIT_EOL_UNSET: + return GIT_EOL_NATIVE == GIT_EOL_CRLF + ? "\r\n" + : "\n"; + + case GIT_EOL_CRLF: + return "\r\n"; + + case GIT_EOL_LF: + return "\n"; + + default: + goto line_ending_error; + } + +line_ending_error: + giterr_set(GITERR_INVALID, "Invalid input to line ending filter"); + return NULL; +} + +static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source) +{ + struct crlf_filter *filter = (struct crlf_filter *)self; + const char *workdir_ending = NULL; + + assert (self && dest && source); + + /* Empty file? Nothing to do. */ + if (git_buf_len(source) == 0) + return 0; + + /* Determine proper line ending */ + workdir_ending = line_ending(filter); + if (!workdir_ending) return -1; + + /* If the line ending is '\n', just copy the input */ + if (!strcmp(workdir_ending, "\n")) + return git_buf_puts(dest, git_buf_cstr(source)); + + return convert_line_endings(dest, source, workdir_ending); +} + +static int find_and_add_filter( + git_vector *filters, git_repository *repo, const char *path, + int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source)) { struct crlf_attrs ca; struct crlf_filter *filter; @@ -196,7 +277,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const /* * Use the core Git logic to see if we should perform CRLF for this file - * based on its attributes & the value of `core.auto_crlf` + * based on its attributes & the value of `core.autocrlf` */ ca.crlf_action = crlf_input_action(&ca); @@ -206,8 +287,7 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const if (ca.crlf_action == GIT_CRLF_GUESS) { int auto_crlf; - if ((error = git_repository__cvar( - &auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) + if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0) return error; if (auto_crlf == GIT_AUTO_CRLF_FALSE) @@ -219,10 +299,19 @@ int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const filter = git__malloc(sizeof(struct crlf_filter)); GITERR_CHECK_ALLOC(filter); - filter->f.apply = &crlf_apply_to_odb; + filter->f.apply = apply; filter->f.do_free = NULL; memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); return git_vector_insert(filters, filter); } +int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path) +{ + return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb); +} + +int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path) +{ + return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir); +} diff --git a/src/date.c b/src/date.c index f0e637a4548..965e6caabc0 100644 --- a/src/date.c +++ b/src/date.c @@ -121,9 +121,9 @@ static const struct { { "IDLE", +12, 0, }, /* International Date Line East */ }; -static int match_string(const char *date, const char *str) +static size_t match_string(const char *date, const char *str) { - int i = 0; + size_t i = 0; for (i = 0; *date; date++, str++, i++) { if (*date == *str) @@ -149,12 +149,12 @@ static int skip_alpha(const char *date) /* * Parse month, weekday, or timezone name */ -static int match_alpha(const char *date, struct tm *tm, int *offset) +static size_t match_alpha(const char *date, struct tm *tm, int *offset) { unsigned int i; for (i = 0; i < 12; i++) { - int match = match_string(date, month_names[i]); + size_t match = match_string(date, month_names[i]); if (match >= 3) { tm->tm_mon = i; return match; @@ -162,7 +162,7 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) } for (i = 0; i < 7; i++) { - int match = match_string(date, weekday_names[i]); + size_t match = match_string(date, weekday_names[i]); if (match >= 3) { tm->tm_wday = i; return match; @@ -170,8 +170,8 @@ static int match_alpha(const char *date, struct tm *tm, int *offset) } for (i = 0; i < ARRAY_SIZE(timezone_names); i++) { - int match = match_string(date, timezone_names[i].name); - if (match >= 3 || match == (int)strlen(timezone_names[i].name)) { + size_t match = match_string(date, timezone_names[i].name); + if (match >= 3 || match == strlen(timezone_names[i].name)) { int off = timezone_names[i].offset; /* This is bogus, but we like summer */ @@ -241,7 +241,7 @@ static int is_date(int year, int month, int day, struct tm *now_tm, time_t now, return 0; } -static int match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) +static size_t match_multi_number(unsigned long num, char c, const char *date, char *end, struct tm *tm) { time_t now; struct tm now_tm; @@ -319,9 +319,9 @@ static int nodate(struct tm *tm) /* * We've seen a digit. Time? Year? Date? */ -static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) +static size_t match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt) { - int n; + size_t n; char *end; unsigned long num; @@ -349,7 +349,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt case '/': case '-': if (isdigit(end[1])) { - int match = match_multi_number(num, *end, date, end, tm); + size_t match = match_multi_number(num, *end, date, end, tm); if (match) return match; } @@ -413,11 +413,11 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt return n; } -static int match_tz(const char *date, int *offp) +static size_t match_tz(const char *date, int *offp) { char *end; int hour = strtoul(date + 1, &end, 10); - int n = end - (date + 1); + size_t n = end - (date + 1); int min = 0; if (n == 4) { @@ -506,7 +506,7 @@ static int parse_date_basic(const char *date, git_time_t *timestamp, int *offset !match_object_header_date(date + 1, timestamp, offset)) return 0; /* success */ for (;;) { - int match = 0; + size_t match = 0; unsigned char c = *date; /* Stop at end of string or newline */ @@ -685,7 +685,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm ; for (i = 0; i < 12; i++) { - int match = match_string(date, month_names[i]); + size_t match = match_string(date, month_names[i]); if (match >= 3) { tm->tm_mon = i; *touched = 1; @@ -694,7 +694,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm } for (s = special; s->name; s++) { - int len = strlen(s->name); + size_t len = strlen(s->name); if (match_string(date, s->name) == len) { s->fn(tm, now, num); *touched = 1; @@ -704,7 +704,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm if (!*num) { for (i = 1; i < 11; i++) { - int len = strlen(number_name[i]); + size_t len = strlen(number_name[i]); if (match_string(date, number_name[i]) == len) { *num = i; *touched = 1; @@ -720,7 +720,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm tl = typelen; while (tl->type) { - int len = strlen(tl->type); + size_t len = strlen(tl->type); if (match_string(date, tl->type) >= len-1) { update_tm(tm, now, tl->length * *num); *num = 0; @@ -731,7 +731,7 @@ static const char *approxidate_alpha(const char *date, struct tm *tm, struct tm } for (i = 0; i < 7; i++) { - int match = match_string(date, weekday_names[i]); + size_t match = match_string(date, weekday_names[i]); if (match >= 3) { int diff, n = *num -1; *num = 0; @@ -783,7 +783,7 @@ static const char *approxidate_digit(const char *date, struct tm *tm, int *num) case '/': case '-': if (isdigit(end[1])) { - int match = match_multi_number(number, *end, date, end, tm); + size_t match = match_multi_number(number, *end, date, end, tm); if (match) return date + match; } diff --git a/src/diff.c b/src/diff.c index 4fea894f83d..499b95b44ee 100644 --- a/src/diff.c +++ b/src/diff.c @@ -6,10 +6,12 @@ */ #include "common.h" #include "git2/diff.h" +#include "git2/oid.h" #include "diff.h" #include "fileops.h" #include "config.h" #include "attr_file.h" +#include "filter.h" static char *diff_prefix_from_pathspec(const git_strarray *pathspec) { @@ -20,14 +22,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec) return NULL; /* diff prefix will only be leading non-wildcards */ - for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan); + 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) - return git_buf_detach(&prefix); + if (prefix.size <= 0) { + git_buf_free(&prefix); + return NULL; + } - 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) @@ -54,7 +63,11 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path) return true; git_vector_foreach(&diff->pathspec, i, match) { - int result = p_fnmatch(match->pattern, path, 0); + 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 && @@ -250,12 +263,14 @@ static int diff_delta__from_two( delta = diff_delta__alloc(diff, status, old_entry->path); GITERR_CHECK_ALLOC(delta); - delta->old_file.mode = old_mode; git_oid_cpy(&delta->old_file.oid, &old_entry->oid); + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; delta->old_file.flags |= GIT_DIFF_FILE_VALID_OID; - delta->new_file.mode = new_mode; git_oid_cpy(&delta->new_file.oid, new_oid ? new_oid : &new_entry->oid); + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; if (new_oid || !git_oid_iszero(&new_entry->oid)) delta->new_file.flags |= GIT_DIFF_FILE_VALID_OID; @@ -304,6 +319,7 @@ static git_diff_list *git_diff_list_alloc( if (diff == NULL) return NULL; + GIT_REFCOUNT_INC(diff); diff->repo = repo; if (git_vector_init(&diff->deltas, 0, diff_delta__cmp) < 0 || @@ -379,15 +395,12 @@ static git_diff_list *git_diff_list_alloc( return NULL; } -void git_diff_list_free(git_diff_list *diff) +static void diff_list_free(git_diff_list *diff) { git_diff_delta *delta; git_attr_fnmatch *match; unsigned int i; - if (!diff) - return; - git_vector_foreach(&diff->deltas, i, delta) { git__free(delta); diff->deltas.contents[i] = NULL; @@ -404,6 +417,14 @@ void git_diff_list_free(git_diff_list *diff) git__free(diff); } +void git_diff_list_free(git_diff_list *diff) +{ + if (!diff) + return; + + GIT_REFCOUNT_DEC(diff, diff_list_free); +} + static int oid_for_workdir_item( git_repository *repo, const git_index_entry *item, @@ -422,14 +443,22 @@ static int oid_for_workdir_item( giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); result = -1; } else { - int fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) - result = fd; - else { - result = git_odb__hashfd( - oid, fd, (size_t)item->file_size, GIT_OBJ_BLOB); - p_close(fd); + git_vector filters = GIT_VECTOR_INIT; + + result = git_filters_load( + &filters, repo, item->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); + p_close(fd); + } } + + git_filters_free(&filters); } git_buf_free(&full_path); @@ -458,7 +487,8 @@ static int maybe_modified( /* on platforms with no symlinks, preserve mode of existing symlinks */ if (S_ISLNK(omode) && S_ISREG(nmode) && - !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS) && + new_iter->type == GIT_ITERATOR_WORKDIR) nmode = omode; /* on platforms with no execmode, just preserve old mode */ @@ -517,7 +547,7 @@ static int maybe_modified( status = GIT_DELTA_UNMODIFIED; else if (git_submodule_lookup(&sub, diff->repo, nitem->path) < 0) return -1; - else if (sub->ignore == GIT_SUBMODULE_IGNORE_ALL) + else if (git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) status = GIT_DELTA_UNMODIFIED; else { /* TODO: support other GIT_SUBMODULE_IGNORE values */ @@ -814,9 +844,9 @@ int git_diff_merge( /* prefix strings also come from old pool, so recreate those.*/ onto->opts.old_prefix = - git_pool_strdup(&onto->pool, onto->opts.old_prefix); + git_pool_strdup_safe(&onto->pool, onto->opts.old_prefix); onto->opts.new_prefix = - git_pool_strdup(&onto->pool, onto->opts.new_prefix); + git_pool_strdup_safe(&onto->pool, onto->opts.new_prefix); } git_vector_foreach(&onto_new, i, delta) @@ -826,4 +856,3 @@ int git_diff_merge( return error; } - diff --git a/src/diff.h b/src/diff.h index 6cc854fbd60..ea38a678f57 100644 --- a/src/diff.h +++ b/src/diff.h @@ -25,7 +25,10 @@ enum { GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ }; +#define MAX_DIFF_FILESIZE 0x20000000 + struct git_diff_list { + git_refcount rc; git_repository *repo; git_diff_options opts; git_vector pathspec; @@ -39,5 +42,27 @@ struct git_diff_list { extern void git_diff__cleanup_modes( uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); +/** + * Return the maximum possible number of files in the diff. + * + * NOTE: This number has to be treated as an upper bound on the number of + * files that have changed if the diff is with the working directory. + * + * Why?! For efficiency, we defer loading the file contents as long as + * possible, so if a file has been "touched" in the working directory and + * then reverted to the original content, it may get stored in the diff list + * as MODIFIED along with a flag that the status should be reconfirmed when + * it is actually loaded into memory. When that load happens, it could get + * flipped to UNMODIFIED. If unmodified files are being skipped, then the + * iterator will skip that file and this number may be too high. + * + * This behavior is true of `git_diff_foreach` as well, but the only + * implication there is that the `progress` value would not advance evenly. + * + * @param iterator The iterator object + * @return The maximum number of files to be iterated over + */ +int git_diff_iterator__max_files(git_diff_iterator *iterator); + #endif diff --git a/src/diff_output.c b/src/diff_output.c index 92f7f8f2f70..58a1a35678d 100644 --- a/src/diff_output.c +++ b/src/diff_output.c @@ -8,6 +8,7 @@ #include "git2/diff.h" #include "git2/attr.h" #include "git2/blob.h" +#include "git2/oid.h" #include "xdiff/xdiff.h" #include #include "diff.h" @@ -15,15 +16,46 @@ #include "fileops.h" #include "filter.h" +/* + * A diff_delta_context represents all of the information that goes into + * processing the diff of an observed file change. In the case of the + * git_diff_foreach() call it is an emphemeral structure that is filled + * in to execute each diff. In the case of a git_diff_iterator, it holds + * most of the information for the diff in progress. + * + * As each delta is processed, it goes through 3 phases: prep, load, exec. + * + * - In the prep phase, we just set the delta and quickly check the file + * attributes to see if it should be treated as binary. + * - In the load phase, we actually load the file content into memory. + * At this point, if we had deferred calculating OIDs, we might have to + * correct the delta to be UNMODIFIED. + * - In the exec phase, we actually run the diff and execute the callbacks. + * For foreach, this is just a pass-through to the user's callbacks. For + * iterators, we record the hunks and data spans into memory. + */ typedef struct { - git_diff_list *diff; - void *cb_data; - git_diff_hunk_fn hunk_cb; - git_diff_data_fn line_cb; - unsigned int index; + git_repository *repo; + git_diff_options *opts; + xdemitconf_t xdiff_config; + xpparam_t xdiff_params; git_diff_delta *delta; + uint32_t prepped : 1; + uint32_t loaded : 1; + uint32_t diffable : 1; + uint32_t diffed : 1; + git_iterator_type_t old_src; + git_iterator_type_t new_src; + git_blob *old_blob; + git_blob *new_blob; + git_map old_data; + git_map new_data; + void *cb_data; + git_diff_hunk_fn per_hunk; + git_diff_data_fn per_line; + int cb_error; git_diff_range range; -} diff_output_info; +} diff_delta_context; static int read_next_int(const char **str, int *value) { @@ -39,71 +71,90 @@ static int read_next_int(const char **str, int *value) return (digits > 0) ? 0 : -1; } -static int diff_output_cb(void *priv, mmbuffer_t *bufs, int len) +static int parse_hunk_header(git_diff_range *range, const char *header) { - diff_output_info *info = priv; - - if (len == 1 && info->hunk_cb) { - git_diff_range range = { -1, 0, -1, 0 }; - const char *scan = bufs[0].ptr; - - /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ - if (*scan != '@') - return -1; - - if (read_next_int(&scan, &range.old_start) < 0) - return -1; - if (*scan == ',' && read_next_int(&scan, &range.old_lines) < 0) - return -1; - - if (read_next_int(&scan, &range.new_start) < 0) - return -1; - if (*scan == ',' && read_next_int(&scan, &range.new_lines) < 0) + /* expect something of the form "@@ -%d[,%d] +%d[,%d] @@" */ + if (*header != '@') + return -1; + if (read_next_int(&header, &range->old_start) < 0) + return -1; + if (*header == ',') { + if (read_next_int(&header, &range->old_lines) < 0) return -1; - - if (range.old_start < 0 || range.new_start < 0) + } else + range->old_lines = 1; + if (read_next_int(&header, &range->new_start) < 0) + return -1; + if (*header == ',') { + if (read_next_int(&header, &range->new_lines) < 0) return -1; + } else + range->new_lines = 1; + if (range->old_start < 0 || range->new_start < 0) + return -1; - memcpy(&info->range, &range, sizeof(git_diff_range)); + return 0; +} - return info->hunk_cb( - info->cb_data, info->delta, &range, bufs[0].ptr, bufs[0].size); +static int format_hunk_header(char *header, size_t len, git_diff_range *range) +{ + if (range->old_lines != 1) { + if (range->new_lines != 1) + return p_snprintf( + header, len, "@@ -%d,%d +%d,%d @@", + range->old_start, range->old_lines, + range->new_start, range->new_lines); + else + return p_snprintf( + header, len, "@@ -%d,%d +%d @@", + range->old_start, range->old_lines, range->new_start); + } else { + if (range->new_lines != 1) + return p_snprintf( + header, len, "@@ -%d +%d,%d @@", + range->old_start, range->new_start, range->new_lines); + else + return p_snprintf( + header, len, "@@ -%d +%d @@", + range->old_start, range->new_start); } +} - if ((len == 2 || len == 3) && info->line_cb) { - int origin; - - /* expect " "/"-"/"+", then data, then maybe newline */ - origin = - (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : - (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : - GIT_DIFF_LINE_CONTEXT; +static bool diff_delta_is_ambiguous(git_diff_delta *delta) +{ + return (git_oid_iszero(&delta->new_file.oid) && + (delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0 && + delta->status == GIT_DELTA_MODIFIED); +} - if (info->line_cb( - info->cb_data, info->delta, &info->range, origin, bufs[1].ptr, bufs[1].size) < 0) - return -1; +static bool diff_delta_should_skip(git_diff_options *opts, git_diff_delta *delta) +{ + if (delta->status == GIT_DELTA_UNMODIFIED && + (opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; - /* This should only happen if we are adding a line that does not - * have a newline at the end and the old code did. In that case, - * we have a ADD with a DEL_EOFNL as a pair. - */ - if (len == 3) { - origin = (origin == GIT_DIFF_LINE_ADDITION) ? - GIT_DIFF_LINE_DEL_EOFNL : GIT_DIFF_LINE_ADD_EOFNL; + if (delta->status == GIT_DELTA_IGNORED && + (opts->flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; - return info->line_cb( - info->cb_data, info->delta, &info->range, origin, bufs[2].ptr, bufs[2].size); - } - } + if (delta->status == GIT_DELTA_UNTRACKED && + (opts->flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; - return 0; + return false; } #define BINARY_DIFF_FLAGS (GIT_DIFF_FILE_BINARY|GIT_DIFF_FILE_NOT_BINARY) -static int update_file_is_binary_by_attr(git_repository *repo, git_diff_file *file) +static int update_file_is_binary_by_attr( + git_repository *repo, git_diff_file *file) { const char *value; + + /* because of blob diffs, cannot assume path is set */ + if (!file->path || !strlen(file->path)) + return 0; + if (git_attr_get(&value, repo, 0, file->path, "diff") < 0) return -1; @@ -121,17 +172,20 @@ static void update_delta_is_binary(git_diff_delta *delta) if ((delta->old_file.flags & GIT_DIFF_FILE_BINARY) != 0 || (delta->new_file.flags & GIT_DIFF_FILE_BINARY) != 0) delta->binary = 1; - else if ((delta->old_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0 || - (delta->new_file.flags & GIT_DIFF_FILE_NOT_BINARY) != 0) + +#define NOT_BINARY_FLAGS (GIT_DIFF_FILE_NOT_BINARY|GIT_DIFF_FILE_NO_DATA) + + else if ((delta->old_file.flags & NOT_BINARY_FLAGS) != 0 && + (delta->new_file.flags & NOT_BINARY_FLAGS) != 0) delta->binary = 0; + /* otherwise leave delta->binary value untouched */ } -static int file_is_binary_by_attr( - git_diff_list *diff, - git_diff_delta *delta) +static int diff_delta_is_binary_by_attr(diff_delta_context *ctxt) { int error = 0, mirror_new; + git_diff_delta *delta = ctxt->delta; delta->binary = -1; @@ -146,7 +200,7 @@ static int file_is_binary_by_attr( } /* check if user is forcing us to text diff these files */ - if (diff->opts.flags & GIT_DIFF_FORCE_TEXT) { + if (ctxt->opts->flags & GIT_DIFF_FORCE_TEXT) { delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; delta->binary = 0; @@ -154,51 +208,61 @@ static int file_is_binary_by_attr( } /* check diff attribute +, -, or 0 */ - if (update_file_is_binary_by_attr(diff->repo, &delta->old_file) < 0) + if (update_file_is_binary_by_attr(ctxt->repo, &delta->old_file) < 0) return -1; mirror_new = (delta->new_file.path == delta->old_file.path || strcmp(delta->new_file.path, delta->old_file.path) == 0); if (mirror_new) - delta->new_file.flags &= (delta->old_file.flags & BINARY_DIFF_FLAGS); + delta->new_file.flags |= (delta->old_file.flags & BINARY_DIFF_FLAGS); else - error = update_file_is_binary_by_attr(diff->repo, &delta->new_file); + error = update_file_is_binary_by_attr(ctxt->repo, &delta->new_file); update_delta_is_binary(delta); return error; } -static int file_is_binary_by_content( - git_diff_delta *delta, - git_map *old_data, - git_map *new_data) +static int diff_delta_is_binary_by_content( + diff_delta_context *ctxt, git_diff_file *file, git_map *map) { git_buf search; - if ((delta->old_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = old_data->data; - search.size = min(old_data->len, 4000); + if ((file->flags & BINARY_DIFF_FLAGS) == 0) { + search.ptr = map->data; + search.size = min(map->len, 4000); if (git_buf_is_binary(&search)) - delta->old_file.flags |= GIT_DIFF_FILE_BINARY; + file->flags |= GIT_DIFF_FILE_BINARY; else - delta->old_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + file->flags |= GIT_DIFF_FILE_NOT_BINARY; } - if ((delta->new_file.flags & BINARY_DIFF_FLAGS) == 0) { - search.ptr = new_data->data; - search.size = min(new_data->len, 4000); + update_delta_is_binary(ctxt->delta); - if (git_buf_is_binary(&search)) - delta->new_file.flags |= GIT_DIFF_FILE_BINARY; - else - delta->new_file.flags |= GIT_DIFF_FILE_NOT_BINARY; + return 0; +} + +static int diff_delta_is_binary_by_size( + diff_delta_context *ctxt, git_diff_file *file) +{ + git_off_t threshold = MAX_DIFF_FILESIZE; + + if ((file->flags & BINARY_DIFF_FLAGS) != 0) + return 0; + + if (ctxt && ctxt->opts) { + if (ctxt->opts->max_size < 0) + return 0; + + if (ctxt->opts->max_size > 0) + threshold = ctxt->opts->max_size; } - update_delta_is_binary(delta); + if (file->size > threshold) + file->flags |= GIT_DIFF_FILE_BINARY; - /* TODO: if value != NULL, implement diff drivers */ + update_delta_is_binary(ctxt->delta); return 0; } @@ -212,7 +276,7 @@ static void setup_xdiff_options( cfg->ctxlen = (!opts || !opts->context_lines) ? 3 : opts->context_lines; cfg->interhunkctxlen = - (!opts || !opts->interhunk_lines) ? 3 : opts->interhunk_lines; + (!opts) ? 0 : opts->interhunk_lines; if (!opts) return; @@ -226,53 +290,147 @@ static void setup_xdiff_options( } static int get_blob_content( - git_repository *repo, - const git_oid *oid, + diff_delta_context *ctxt, + git_diff_file *file, git_map *map, git_blob **blob) { - if (git_oid_iszero(oid)) + int error; + git_odb_object *odb_obj = NULL; + + if (git_oid_iszero(&file->oid)) return 0; - if (git_blob_lookup(blob, repo, oid) < 0) - return -1; + if (!file->size) { + git_odb *odb; + size_t len; + git_otype type; + + /* peek at object header to avoid loading if too large */ + if ((error = git_repository_odb__weakptr(&odb, ctxt->repo)) < 0 || + (error = git_odb__read_header_or_object( + &odb_obj, &len, &type, odb, &file->oid)) < 0) + return error; + + assert(type == GIT_OBJ_BLOB); + + file->size = len; + } + + /* if blob is too large to diff, mark as binary */ + if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0) + return error; + if (ctxt->delta->binary == 1) + return 0; + + if (odb_obj != NULL) { + error = git_object__from_odb_object( + (git_object **)blob, ctxt->repo, odb_obj, GIT_OBJ_BLOB); + git_odb_object_free(odb_obj); + } else + error = git_blob_lookup(blob, ctxt->repo, &file->oid); + + if (error) + return error; map->data = (void *)git_blob_rawcontent(*blob); map->len = git_blob_rawsize(*blob); - return 0; + + return diff_delta_is_binary_by_content(ctxt, file, map); } static int get_workdir_content( - git_repository *repo, + diff_delta_context *ctxt, git_diff_file *file, git_map *map) { int error = 0; git_buf path = GIT_BUF_INIT; + const char *wd = git_repository_workdir(ctxt->repo); - if (git_buf_joinpath(&path, git_repository_workdir(repo), file->path) < 0) + if (git_buf_joinpath(&path, wd, file->path) < 0) return -1; if (S_ISLNK(file->mode)) { - ssize_t read_len; + ssize_t alloc_len, read_len; file->flags |= GIT_DIFF_FILE_FREE_DATA; file->flags |= GIT_DIFF_FILE_BINARY; - map->data = git__malloc((size_t)file->size + 1); + /* link path on disk could be UTF-16, so prepare a buffer that is + * big enough to handle some UTF-8 data expansion + */ + alloc_len = (ssize_t)(file->size * 2) + 1; + + map->data = git__malloc(alloc_len); GITERR_CHECK_ALLOC(map->data); - read_len = p_readlink(path.ptr, map->data, (size_t)file->size + 1); - if (read_len != (ssize_t)file->size) { + read_len = p_readlink(path.ptr, map->data, (int)alloc_len); + if (read_len < 0) { giterr_set(GITERR_OS, "Failed to read symlink '%s'", file->path); error = -1; - } else - map->len = read_len; + goto cleanup; + } + + map->len = read_len; } else { - error = git_futils_mmap_ro_file(map, path.ptr); - file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + git_file fd = git_futils_open_ro(path.ptr); + git_vector filters = GIT_VECTOR_INIT; + + if (fd < 0) { + error = fd; + goto cleanup; + } + + if (!file->size) + file->size = git_futils_filesize(fd); + + if ((error = diff_delta_is_binary_by_size(ctxt, file)) < 0 || + ctxt->delta->binary == 1) + goto close_and_cleanup; + + error = git_filters_load( + &filters, ctxt->repo, file->path, GIT_FILTER_TO_ODB); + if (error < 0) + goto close_and_cleanup; + + if (error == 0) { /* note: git_filters_load returns filter count */ + error = git_futils_mmap_ro(map, fd, 0, (size_t)file->size); + file->flags |= GIT_DIFF_FILE_UNMAP_DATA; + } else { + git_buf raw = GIT_BUF_INIT, filtered = GIT_BUF_INIT; + + if (!(error = git_futils_readbuffer_fd(&raw, fd, (size_t)file->size)) && + !(error = git_filters_apply(&filtered, &raw, &filters))) + { + map->len = git_buf_len(&filtered); + map->data = git_buf_detach(&filtered); + + file->flags |= GIT_DIFF_FILE_FREE_DATA; + } + + git_buf_free(&raw); + git_buf_free(&filtered); + } + +close_and_cleanup: + git_filters_free(&filters); + p_close(fd); } + + /* once data is loaded, update OID if we didn't have it previously */ + if (!error && (file->flags & GIT_DIFF_FILE_VALID_OID) == 0) { + error = git_odb_hash( + &file->oid, map->data, map->len, GIT_OBJ_BLOB); + if (!error) + file->flags |= GIT_DIFF_FILE_VALID_OID; + } + + if (!error) + error = diff_delta_is_binary_by_content(ctxt, file, map); + +cleanup: git_buf_free(&path); return error; } @@ -284,186 +442,324 @@ static void release_content(git_diff_file *file, git_map *map, git_blob *blob) if (file->flags & GIT_DIFF_FILE_FREE_DATA) { git__free(map->data); - map->data = NULL; + map->data = ""; + map->len = 0; file->flags &= ~GIT_DIFF_FILE_FREE_DATA; } else if (file->flags & GIT_DIFF_FILE_UNMAP_DATA) { git_futils_mmap_free(map); - map->data = NULL; + map->data = ""; + map->len = 0; file->flags &= ~GIT_DIFF_FILE_UNMAP_DATA; } } -static void fill_map_from_mmfile(git_map *dst, mmfile_t *src) { - assert(dst && src); +static void diff_delta_init_context( + diff_delta_context *ctxt, + git_repository *repo, + git_diff_options *opts, + git_iterator_type_t old_src, + git_iterator_type_t new_src) +{ + memset(ctxt, 0, sizeof(diff_delta_context)); + + ctxt->repo = repo; + ctxt->opts = opts; + ctxt->old_src = old_src; + ctxt->new_src = new_src; - dst->data = src->ptr; - dst->len = src->size; -#ifdef GIT_WIN32 - dst->fmh = NULL; -#endif + setup_xdiff_options(opts, &ctxt->xdiff_config, &ctxt->xdiff_params); } -int git_diff_foreach( - git_diff_list *diff, - void *data, - git_diff_file_fn file_cb, - git_diff_hunk_fn hunk_cb, - git_diff_data_fn line_cb) +static void diff_delta_init_context_from_diff_list( + diff_delta_context *ctxt, + git_diff_list *diff) { - int error = 0; - diff_output_info info; - git_diff_delta *delta; - xpparam_t xdiff_params; - xdemitconf_t xdiff_config; - xdemitcb_t xdiff_callback; + diff_delta_init_context( + ctxt, diff->repo, &diff->opts, diff->old_src, diff->new_src); +} - info.diff = diff; - info.cb_data = data; - info.hunk_cb = hunk_cb; - info.line_cb = line_cb; +static void diff_delta_unload(diff_delta_context *ctxt) +{ + ctxt->diffed = 0; - setup_xdiff_options(&diff->opts, &xdiff_config, &xdiff_params); - memset(&xdiff_callback, 0, sizeof(xdiff_callback)); - xdiff_callback.outf = diff_output_cb; - xdiff_callback.priv = &info; + if (ctxt->loaded) { + release_content(&ctxt->delta->old_file, &ctxt->old_data, ctxt->old_blob); + release_content(&ctxt->delta->new_file, &ctxt->new_data, ctxt->new_blob); + ctxt->loaded = 0; + } - git_vector_foreach(&diff->deltas, info.index, delta) { - git_blob *old_blob = NULL, *new_blob = NULL; - git_map old_data, new_data; - mmfile_t old_xdiff_data, new_xdiff_data; + ctxt->delta = NULL; + ctxt->prepped = 0; +} - if (delta->status == GIT_DELTA_UNMODIFIED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - continue; +static int diff_delta_prep(diff_delta_context *ctxt) +{ + int error; - if (delta->status == GIT_DELTA_IGNORED && - (diff->opts.flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - continue; + if (ctxt->prepped || !ctxt->delta) + return 0; - if (delta->status == GIT_DELTA_UNTRACKED && - (diff->opts.flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - continue; + error = diff_delta_is_binary_by_attr(ctxt); - if ((error = file_is_binary_by_attr(diff, delta)) < 0) - goto cleanup; + ctxt->prepped = !error; - old_data.data = ""; - old_data.len = 0; - new_data.data = ""; - new_data.len = 0; + return error; +} - /* TODO: Partial blob reading to defer loading whole blob. - * I.e. I want a blob with just the first 4kb loaded, then - * later on I will read the rest of the blob if needed. - */ +static int diff_delta_load(diff_delta_context *ctxt) +{ + int error = 0; + git_diff_delta *delta = ctxt->delta; + bool check_if_unmodified = false; - /* map files */ - if (delta->binary != 1 && - (hunk_cb || line_cb || git_oid_iszero(&delta->old_file.oid)) && - (delta->status == GIT_DELTA_DELETED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (diff->old_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content(diff->repo, &delta->old_file, &old_data); - else - error = get_blob_content( - diff->repo, &delta->old_file.oid, &old_data, &old_blob); + if (ctxt->loaded || !ctxt->delta) + return 0; - if (error < 0) - goto cleanup; - } + if (!ctxt->prepped && (error = diff_delta_prep(ctxt)) < 0) + goto cleanup; - if (delta->binary != 1 && - (hunk_cb || line_cb || git_oid_iszero(&delta->new_file.oid)) && - (delta->status == GIT_DELTA_ADDED || - delta->status == GIT_DELTA_MODIFIED)) - { - if (diff->new_src == GIT_ITERATOR_WORKDIR) - error = get_workdir_content(diff->repo, &delta->new_file, &new_data); - else - error = get_blob_content( - diff->repo, &delta->new_file.oid, &new_data, &new_blob); + ctxt->old_data.data = ""; + ctxt->old_data.len = 0; + ctxt->old_blob = NULL; - if (error < 0) - goto cleanup; + ctxt->new_data.data = ""; + ctxt->new_data.len = 0; + ctxt->new_blob = NULL; - if ((delta->new_file.flags & GIT_DIFF_FILE_VALID_OID) == 0) { - error = git_odb_hash( - &delta->new_file.oid, new_data.data, new_data.len, GIT_OBJ_BLOB); - - if (error < 0) - goto cleanup; - - /* since we did not have the definitive oid, we may have - * incorrect status and need to skip this item. - */ - if (delta->old_file.mode == delta->new_file.mode && - !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) - { - delta->status = GIT_DELTA_UNMODIFIED; - if ((diff->opts.flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - goto cleanup; - } - } - } + if (delta->binary == 1) + goto cleanup; - /* if we have not already decided whether file is binary, - * check the first 4K for nul bytes to decide... - */ - if (delta->binary == -1) { - error = file_is_binary_by_content( - delta, &old_data, &new_data); - if (error < 0) - goto cleanup; - } + switch (delta->status) { + case GIT_DELTA_ADDED: + delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; + case GIT_DELTA_DELETED: + delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; + case GIT_DELTA_MODIFIED: + break; + default: + delta->new_file.flags |= GIT_DIFF_FILE_NO_DATA; + delta->old_file.flags |= GIT_DIFF_FILE_NO_DATA; + break; + } - /* TODO: if ignore_whitespace is set, then we *must* do text - * diffs to tell if a file has really been changed. - */ +#define CHECK_UNMODIFIED (GIT_DIFF_FILE_NO_DATA | GIT_DIFF_FILE_VALID_OID) - if (file_cb != NULL) { - error = file_cb( - data, delta, (float)info.index / diff->deltas.length); - if (error < 0) - goto cleanup; - } + check_if_unmodified = + (delta->old_file.flags & CHECK_UNMODIFIED) == 0 && + (delta->new_file.flags & CHECK_UNMODIFIED) == 0; + + /* Always try to load workdir content first, since it may need to be + * filtered (and hence use 2x memory) and we want to minimize the max + * memory footprint during diff. + */ - /* don't do hunk and line diffs if file is binary */ + if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->old_src == GIT_ITERATOR_WORKDIR) { + if ((error = get_workdir_content( + ctxt, &delta->old_file, &ctxt->old_data)) < 0) + goto cleanup; if (delta->binary == 1) goto cleanup; + } - /* nothing to do if we did not get data */ - if (!old_data.len && !new_data.len) + if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->new_src == GIT_ITERATOR_WORKDIR) { + if ((error = get_workdir_content( + ctxt, &delta->new_file, &ctxt->new_data)) < 0) goto cleanup; + if (delta->binary == 1) + goto cleanup; + } + + if ((delta->old_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->old_src != GIT_ITERATOR_WORKDIR) { + if ((error = get_blob_content( + ctxt, &delta->old_file, &ctxt->old_data, &ctxt->old_blob)) < 0) + goto cleanup; + if (delta->binary == 1) + goto cleanup; + } + + if ((delta->new_file.flags & GIT_DIFF_FILE_NO_DATA) == 0 && + ctxt->new_src != GIT_ITERATOR_WORKDIR) { + if ((error = get_blob_content( + ctxt, &delta->new_file, &ctxt->new_data, &ctxt->new_blob)) < 0) + goto cleanup; + if (delta->binary == 1) + goto cleanup; + } + + /* if we did not previously have the definitive oid, we may have + * incorrect status and need to switch this to UNMODIFIED. + */ + if (check_if_unmodified && + delta->old_file.mode == delta->new_file.mode && + !git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) + { + delta->status = GIT_DELTA_UNMODIFIED; - /* nothing to do if only diff was a mode change */ - if (!git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)) + if ((ctxt->opts->flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) goto cleanup; + } + +cleanup: + /* last change to update binary flag */ + if (delta->binary == -1) + update_delta_is_binary(delta); + + ctxt->loaded = !error; + + /* flag if we would want to diff the contents of these files */ + if (ctxt->loaded) + ctxt->diffable = + (delta->binary != 1 && + delta->status != GIT_DELTA_UNMODIFIED && + (ctxt->old_data.len || ctxt->new_data.len) && + git_oid_cmp(&delta->old_file.oid, &delta->new_file.oid)); + + return error; +} + +static int diff_delta_cb(void *priv, mmbuffer_t *bufs, int len) +{ + diff_delta_context *ctxt = priv; + + if (len == 1) { + if ((ctxt->cb_error = parse_hunk_header(&ctxt->range, bufs[0].ptr)) < 0) + return ctxt->cb_error; + + if (ctxt->per_hunk != NULL && + ctxt->per_hunk(ctxt->cb_data, ctxt->delta, &ctxt->range, + bufs[0].ptr, bufs[0].size)) + ctxt->cb_error = GIT_EUSER; + } + + if (len == 2 || len == 3) { + /* expect " "/"-"/"+", then data */ + char origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_ADDITION : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_DELETION : + GIT_DIFF_LINE_CONTEXT; + + if (ctxt->per_line != NULL && + ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, + bufs[1].ptr, bufs[1].size)) + ctxt->cb_error = GIT_EUSER; + } + + if (len == 3 && !ctxt->cb_error) { + /* If we have a '+' and a third buf, then we have added a line + * without a newline and the old code had one, so DEL_EOFNL. + * If we have a '-' and a third buf, then we have removed a line + * with out a newline but added a blank line, so ADD_EOFNL. + */ + char origin = + (*bufs[0].ptr == '+') ? GIT_DIFF_LINE_DEL_EOFNL : + (*bufs[0].ptr == '-') ? GIT_DIFF_LINE_ADD_EOFNL : + GIT_DIFF_LINE_CONTEXT; + + if (ctxt->per_line != NULL && + ctxt->per_line(ctxt->cb_data, ctxt->delta, &ctxt->range, origin, + bufs[2].ptr, bufs[2].size)) + ctxt->cb_error = GIT_EUSER; + } + + return ctxt->cb_error; +} - assert(hunk_cb || line_cb); +static int diff_delta_exec( + diff_delta_context *ctxt, + void *cb_data, + git_diff_hunk_fn per_hunk, + git_diff_data_fn per_line) +{ + int error = 0; + xdemitcb_t xdiff_callback; + mmfile_t old_xdiff_data, new_xdiff_data; + + if (ctxt->diffed || !ctxt->delta) + return 0; + + if (!ctxt->loaded && (error = diff_delta_load(ctxt)) < 0) + goto cleanup; - info.delta = delta; - old_xdiff_data.ptr = old_data.data; - old_xdiff_data.size = old_data.len; - new_xdiff_data.ptr = new_data.data; - new_xdiff_data.size = new_data.len; + if (!ctxt->diffable) + return 0; + + ctxt->cb_data = cb_data; + ctxt->per_hunk = per_hunk; + ctxt->per_line = per_line; + ctxt->cb_error = 0; + + memset(&xdiff_callback, 0, sizeof(xdiff_callback)); + xdiff_callback.outf = diff_delta_cb; + xdiff_callback.priv = ctxt; - xdl_diff(&old_xdiff_data, &new_xdiff_data, - &xdiff_params, &xdiff_config, &xdiff_callback); + old_xdiff_data.ptr = ctxt->old_data.data; + old_xdiff_data.size = ctxt->old_data.len; + new_xdiff_data.ptr = ctxt->new_data.data; + new_xdiff_data.size = ctxt->new_data.len; + + xdl_diff(&old_xdiff_data, &new_xdiff_data, + &ctxt->xdiff_params, &ctxt->xdiff_config, &xdiff_callback); + + error = ctxt->cb_error; cleanup: - release_content(&delta->old_file, &old_data, old_blob); - release_content(&delta->new_file, &new_data, new_blob); + ctxt->diffed = !error; + + return error; +} + +int git_diff_foreach( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error = 0; + diff_delta_context ctxt; + size_t idx; + + diff_delta_init_context_from_diff_list(&ctxt, diff); + + git_vector_foreach(&diff->deltas, idx, ctxt.delta) { + if (diff_delta_is_ambiguous(ctxt.delta)) + if ((error = diff_delta_load(&ctxt)) < 0) + goto cleanup; + + if (diff_delta_should_skip(ctxt.opts, ctxt.delta)) + continue; + + if ((error = diff_delta_load(&ctxt)) < 0) + goto cleanup; + + if (file_cb != NULL && + file_cb(data, ctxt.delta, (float)idx / diff->deltas.length) != 0) + { + error = GIT_EUSER; + goto cleanup; + } + + error = diff_delta_exec(&ctxt, data, hunk_cb, line_cb); + +cleanup: + diff_delta_unload(&ctxt); if (error < 0) break; } + if (error == GIT_EUSER) + giterr_clear(); + return error; } - typedef struct { git_diff_list *diff; git_diff_data_fn print_cb; @@ -524,7 +820,14 @@ static int print_compact(void *data, git_diff_delta *delta, float progress) if (git_buf_oom(pi->buf)) return -1; - return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, + git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); + return GIT_EUSER; + } + + return 0; } int git_diff_print_compact( @@ -586,7 +889,6 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) const char *oldpath = delta->old_file.path; const char *newpfx = pi->diff->opts.new_prefix; const char *newpath = delta->new_file.path; - int result; GIT_UNUSED(progress); @@ -619,9 +921,11 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) if (git_buf_oom(pi->buf)) return -1; - result = pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); - if (result < 0) - return result; + if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_FILE_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); + return GIT_EUSER; + } if (delta->binary != 1) return 0; @@ -633,7 +937,14 @@ static int print_patch_file(void *data, git_diff_delta *delta, float progress) if (git_buf_oom(pi->buf)) return -1; - return pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (pi->print_cb(pi->cb_data, delta, NULL, GIT_DIFF_LINE_BINARY, + git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); + return GIT_EUSER; + } + + return 0; } static int print_patch_hunk( @@ -649,7 +960,14 @@ static int print_patch_hunk( if (git_buf_printf(pi->buf, "%.*s", (int)header_len, header) < 0) return -1; - return pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (pi->print_cb(pi->cb_data, d, r, GIT_DIFF_LINE_HUNK_HDR, + git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); + return GIT_EUSER; + } + + return 0; } static int print_patch_line( @@ -674,7 +992,14 @@ static int print_patch_line( if (git_buf_oom(pi->buf)) return -1; - return pi->print_cb(pi->cb_data, delta, range, line_origin, git_buf_cstr(pi->buf), git_buf_len(pi->buf)); + if (pi->print_cb(pi->cb_data, delta, range, line_origin, + git_buf_cstr(pi->buf), git_buf_len(pi->buf))) + { + giterr_clear(); + return GIT_EUSER; + } + + return 0; } int git_diff_print_patch( @@ -699,6 +1024,45 @@ int git_diff_print_patch( return error; } +int git_diff_entrycount(git_diff_list *diff, int delta_t) +{ + int count = 0; + unsigned int i; + git_diff_delta *delta; + + assert(diff); + + git_vector_foreach(&diff->deltas, i, delta) { + if (diff_delta_should_skip(&diff->opts, delta)) + continue; + + if (delta_t < 0 || delta->status == (git_delta_t)delta_t) + count++; + } + + /* It is possible that this has overcounted the number of diffs because + * there may be entries that are marked as MODIFIED due to differences + * in stat() output that will turn out to be the same once we calculate + * the actual SHA of the data on disk. + */ + + return count; +} + +static void set_data_from_blob( + git_blob *blob, git_map *map, git_diff_file *file) +{ + if (blob) { + map->data = (char *)git_blob_rawcontent(blob); + file->size = map->len = git_blob_rawsize(blob); + git_oid_cpy(&file->oid, git_object_id((const git_object *)blob)); + } else { + map->data = ""; + file->size = map->len = 0; + file->flags |= GIT_DIFF_FILE_NO_DATA; + } +} + int git_diff_blobs( git_blob *old_blob, git_blob *new_blob, @@ -708,16 +1072,11 @@ int git_diff_blobs( git_diff_hunk_fn hunk_cb, git_diff_data_fn line_cb) { - diff_output_info info; + int error; + diff_delta_context ctxt; git_diff_delta delta; - mmfile_t old_data, new_data; - git_map old_map, new_map; - xpparam_t xdiff_params; - xdemitconf_t xdiff_config; - xdemitcb_t xdiff_callback; git_blob *new, *old; - - memset(&delta, 0, sizeof(delta)); + git_repository *repo; new = new_blob; old = old_blob; @@ -728,25 +1087,23 @@ int git_diff_blobs( new = swap; } - if (old) { - old_data.ptr = (char *)git_blob_rawcontent(old); - old_data.size = git_blob_rawsize(old); - git_oid_cpy(&delta.old_file.oid, git_object_id((const git_object *)old)); - } else { - old_data.ptr = ""; - old_data.size = 0; - } + if (new) + repo = git_object_owner((git_object *)new); + else if (old) + repo = git_object_owner((git_object *)old); + else + repo = NULL; - if (new) { - new_data.ptr = (char *)git_blob_rawcontent(new); - new_data.size = git_blob_rawsize(new); - git_oid_cpy(&delta.new_file.oid, git_object_id((const git_object *)new)); - } else { - new_data.ptr = ""; - new_data.size = 0; - } + diff_delta_init_context( + &ctxt, repo, options, GIT_ITERATOR_TREE, GIT_ITERATOR_TREE); /* populate a "fake" delta record */ + + memset(&delta, 0, sizeof(delta)); + + set_data_from_blob(old, &ctxt.old_data, &delta.old_file); + set_data_from_blob(new, &ctxt.new_data, &delta.new_file); + delta.status = new ? (old ? GIT_DELTA_MODIFIED : GIT_DELTA_ADDED) : (old ? GIT_DELTA_DELETED : GIT_DELTA_UNTRACKED); @@ -754,41 +1111,388 @@ int git_diff_blobs( if (git_oid_cmp(&delta.new_file.oid, &delta.old_file.oid) == 0) delta.status = GIT_DELTA_UNMODIFIED; - delta.old_file.size = old_data.size; - delta.new_file.size = new_data.size; + ctxt.delta = δ + + if ((error = diff_delta_prep(&ctxt)) < 0) + goto cleanup; + + if (delta.binary == -1) { + if ((error = diff_delta_is_binary_by_content( + &ctxt, &delta.old_file, &ctxt.old_data)) < 0 || + (error = diff_delta_is_binary_by_content( + &ctxt, &delta.new_file, &ctxt.new_data)) < 0) + goto cleanup; + } + + ctxt.loaded = 1; + ctxt.diffable = (delta.binary != 1 && delta.status != GIT_DELTA_UNMODIFIED); + + /* do diffs */ + + if (file_cb != NULL && file_cb(cb_data, &delta, 1)) { + error = GIT_EUSER; + goto cleanup; + } + + error = diff_delta_exec(&ctxt, cb_data, hunk_cb, line_cb); + +cleanup: + if (error == GIT_EUSER) + giterr_clear(); + + diff_delta_unload(&ctxt); + + return error; +} + +typedef struct diffiter_line diffiter_line; +struct diffiter_line { + diffiter_line *next; + char origin; + const char *ptr; + size_t len; +}; + +typedef struct diffiter_hunk diffiter_hunk; +struct diffiter_hunk { + diffiter_hunk *next; + git_diff_range range; + diffiter_line *line_head; + size_t line_count; +}; + +struct git_diff_iterator { + git_diff_list *diff; + diff_delta_context ctxt; + size_t file_index; + size_t next_index; + git_pool hunks; + size_t hunk_count; + diffiter_hunk *hunk_head; + diffiter_hunk *hunk_curr; + char hunk_header[128]; + git_pool lines; + diffiter_line *line_curr; +}; + +typedef struct { + git_diff_iterator *iter; + diffiter_hunk *last_hunk; + diffiter_line *last_line; +} diffiter_cb_info; + +static int diffiter_hunk_cb( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + const char *header, + size_t header_len) +{ + diffiter_cb_info *info = cb_data; + git_diff_iterator *iter = info->iter; + diffiter_hunk *hunk; - fill_map_from_mmfile(&old_map, &old_data); - fill_map_from_mmfile(&new_map, &new_data); + GIT_UNUSED(delta); + GIT_UNUSED(header); + GIT_UNUSED(header_len); - if (file_is_binary_by_content(&delta, &old_map, &new_map) < 0) + if ((hunk = git_pool_mallocz(&iter->hunks, 1)) == NULL) { + iter->ctxt.cb_error = -1; return -1; + } - if (file_cb != NULL) { - int error = file_cb(cb_data, &delta, 1); - if (error < 0) - return error; + if (info->last_hunk) + info->last_hunk->next = hunk; + info->last_hunk = hunk; + info->last_line = NULL; + + memcpy(&hunk->range, range, sizeof(hunk->range)); + + iter->hunk_count++; + + /* adding first hunk to list */ + if (iter->hunk_head == NULL) { + iter->hunk_head = hunk; + iter->hunk_curr = NULL; } - /* don't do hunk and line diffs if the two blobs are identical */ - if (delta.status == GIT_DELTA_UNMODIFIED) - return 0; + return 0; +} - /* don't do hunk and line diffs if file is binary */ - if (delta.binary == 1) +static int diffiter_line_cb( + void *cb_data, + git_diff_delta *delta, + git_diff_range *range, + char line_origin, + const char *content, + size_t content_len) +{ + diffiter_cb_info *info = cb_data; + git_diff_iterator *iter = info->iter; + diffiter_line *line; + + GIT_UNUSED(delta); + GIT_UNUSED(range); + + if ((line = git_pool_mallocz(&iter->lines, 1)) == NULL) { + iter->ctxt.cb_error = -1; + return -1; + } + + if (info->last_line) + info->last_line->next = line; + info->last_line = line; + + line->origin = line_origin; + line->ptr = content; + line->len = content_len; + + info->last_hunk->line_count++; + + if (info->last_hunk->line_head == NULL) + info->last_hunk->line_head = line; + + return 0; +} + +static int diffiter_do_diff_file(git_diff_iterator *iter) +{ + int error; + diffiter_cb_info info; + + if (iter->ctxt.diffed || !iter->ctxt.delta) return 0; - info.diff = NULL; - info.delta = δ - info.cb_data = cb_data; - info.hunk_cb = hunk_cb; - info.line_cb = line_cb; + memset(&info, 0, sizeof(info)); + info.iter = iter; - setup_xdiff_options(options, &xdiff_config, &xdiff_params); - memset(&xdiff_callback, 0, sizeof(xdiff_callback)); - xdiff_callback.outf = diff_output_cb; - xdiff_callback.priv = &info; + error = diff_delta_exec( + &iter->ctxt, &info, diffiter_hunk_cb, diffiter_line_cb); + + if (error == GIT_EUSER) + error = iter->ctxt.cb_error; + + return error; +} + +static void diffiter_do_unload_file(git_diff_iterator *iter) +{ + if (iter->ctxt.loaded) { + diff_delta_unload(&iter->ctxt); + + git_pool_clear(&iter->lines); + git_pool_clear(&iter->hunks); + } + + iter->ctxt.delta = NULL; + iter->hunk_curr = iter->hunk_head = NULL; + iter->hunk_count = 0; + iter->line_curr = NULL; +} + +int git_diff_iterator_new( + git_diff_iterator **iterator_ptr, + git_diff_list *diff) +{ + git_diff_iterator *iter; + + assert(diff && iterator_ptr); + + *iterator_ptr = NULL; - xdl_diff(&old_data, &new_data, &xdiff_params, &xdiff_config, &xdiff_callback); + iter = git__malloc(sizeof(git_diff_iterator)); + GITERR_CHECK_ALLOC(iter); + + memset(iter, 0, sizeof(*iter)); + + iter->diff = diff; + GIT_REFCOUNT_INC(iter->diff); + + diff_delta_init_context_from_diff_list(&iter->ctxt, diff); + + if (git_pool_init(&iter->hunks, sizeof(diffiter_hunk), 0) < 0 || + git_pool_init(&iter->lines, sizeof(diffiter_line), 0) < 0) + goto fail; + + *iterator_ptr = iter; return 0; + +fail: + git_diff_iterator_free(iter); + + return -1; +} + +void git_diff_iterator_free(git_diff_iterator *iter) +{ + diffiter_do_unload_file(iter); + git_diff_list_free(iter->diff); /* decrement ref count */ + git__free(iter); +} + +float git_diff_iterator_progress(git_diff_iterator *iter) +{ + return (float)iter->next_index / (float)iter->diff->deltas.length; +} + +int git_diff_iterator__max_files(git_diff_iterator *iter) +{ + return (int)iter->diff->deltas.length; +} + +int git_diff_iterator_num_hunks_in_file(git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + return (error != 0) ? error : (int)iter->hunk_count; +} + +int git_diff_iterator_num_lines_in_hunk(git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + if (error) + return error; + + if (iter->hunk_curr) + return (int)iter->hunk_curr->line_count; + if (iter->hunk_head) + return (int)iter->hunk_head->line_count; + return 0; +} + +int git_diff_iterator_next_file( + git_diff_delta **delta_ptr, + git_diff_iterator *iter) +{ + int error = 0; + + assert(iter); + + iter->file_index = iter->next_index; + + diffiter_do_unload_file(iter); + + while (!error) { + iter->ctxt.delta = git_vector_get(&iter->diff->deltas, iter->file_index); + if (!iter->ctxt.delta) { + error = GIT_ITEROVER; + break; + } + + if (diff_delta_is_ambiguous(iter->ctxt.delta) && + (error = diff_delta_load(&iter->ctxt)) < 0) + break; + + if (!diff_delta_should_skip(iter->ctxt.opts, iter->ctxt.delta)) + break; + + iter->file_index++; + } + + if (!error) { + iter->next_index = iter->file_index + 1; + + error = diff_delta_prep(&iter->ctxt); + } + + if (iter->ctxt.delta == NULL) { + iter->hunk_curr = iter->hunk_head = NULL; + iter->line_curr = NULL; + } + + if (delta_ptr != NULL) + *delta_ptr = !error ? iter->ctxt.delta : NULL; + + return error; +} + +int git_diff_iterator_next_hunk( + git_diff_range **range_ptr, + const char **header, + size_t *header_len, + git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + git_diff_range *range; + + if (error) + return error; + + if (iter->hunk_curr == NULL) { + if (iter->hunk_head == NULL) + goto no_more_hunks; + iter->hunk_curr = iter->hunk_head; + } else { + if (iter->hunk_curr->next == NULL) + goto no_more_hunks; + iter->hunk_curr = iter->hunk_curr->next; + } + + range = &iter->hunk_curr->range; + + if (range_ptr) + *range_ptr = range; + + if (header) { + int out = format_hunk_header( + iter->hunk_header, sizeof(iter->hunk_header), range); + + /* TODO: append function name to header */ + + *(iter->hunk_header + out++) = '\n'; + + *header = iter->hunk_header; + + if (header_len) + *header_len = (size_t)out; + } + + iter->line_curr = iter->hunk_curr->line_head; + + return error; + +no_more_hunks: + if (range_ptr) *range_ptr = NULL; + if (header) *header = NULL; + if (header_len) *header_len = 0; + iter->line_curr = NULL; + + return GIT_ITEROVER; +} + +int git_diff_iterator_next_line( + char *line_origin, /**< GIT_DIFF_LINE_... value from above */ + const char **content_ptr, + size_t *content_len, + git_diff_iterator *iter) +{ + int error = diffiter_do_diff_file(iter); + + if (error) + return error; + + /* if the user has not called next_hunk yet, call it implicitly (OK?) */ + if (iter->hunk_curr == NULL) { + error = git_diff_iterator_next_hunk(NULL, NULL, NULL, iter); + if (error) + return error; + } + + if (iter->line_curr == NULL) { + if (line_origin) *line_origin = GIT_DIFF_LINE_CONTEXT; + if (content_ptr) *content_ptr = NULL; + if (content_len) *content_len = 0; + return GIT_ITEROVER; + } + + if (line_origin) + *line_origin = iter->line_curr->origin; + if (content_ptr) + *content_ptr = iter->line_curr->ptr; + if (content_len) + *content_len = iter->line_curr->len; + + iter->line_curr = iter->line_curr->next; + + return error; } diff --git a/src/errors.c b/src/errors.c index d43d7d9b56e..802ad36476b 100644 --- a/src/errors.c +++ b/src/errors.c @@ -110,6 +110,11 @@ void giterr_set_regex(const regex_t *regex, int error_code) void giterr_clear(void) { GIT_GLOBAL->last_error = NULL; + + errno = 0; +#ifdef GIT_WIN32 + SetLastError(0); +#endif } const git_error *giterr_last(void) diff --git a/src/fetch.c b/src/fetch.c index 603284842ae..98e1f0b13aa 100644 --- a/src/fetch.c +++ b/src/fetch.c @@ -5,7 +5,6 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/remote.h" #include "git2/oid.h" #include "git2/refs.h" #include "git2/revwalk.h" @@ -18,6 +17,7 @@ #include "pack.h" #include "fetch.h" #include "netops.h" +#include "pkt.h" struct filter_payload { git_remote *remote; @@ -70,7 +70,62 @@ static int filter_wants(git_remote *remote) if (git_repository_odb__weakptr(&p.odb, remote->repo) < 0) return -1; - return remote->transport->ls(remote->transport, &filter_ref__cb, &p); + return git_remote_ls(remote, filter_ref__cb, &p); +} + +/* 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; } /* @@ -81,6 +136,12 @@ static int filter_wants(git_remote *remote) 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, 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"); @@ -92,66 +153,227 @@ int git_fetch_negotiate(git_remote *remote) return 0; /* - * Now we have everything set up so we can start tell the server - * what we want and what we have. + * 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. */ - return t->negotiate_fetch(t, remote->repo, &remote->refs); + i = 0; + while ((error = git_revwalk_next(&oid, walk)) == 0) { + git_pkt_buffer_have(&oid, &data); + i++; + if (i % 20 == 0) { + 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->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 -1; } int git_fetch_download_pack(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) { + git_transport *t = remote->transport; + if(!remote->need_pack) return 0; - return remote->transport->download_pack(remote->transport, remote->repo, bytes, stats); + 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_indexer_stream *idx, gitno_buffer *buf, git_off_t *bytes, git_indexer_stats *stats) +{ + int recvd; + + do { + 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( - const char *buffered, - size_t buffered_size, git_transport *t, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) { - int recvd; - char buff[1024]; - gitno_buffer buf; git_buf path = GIT_BUF_INIT; + gitno_buffer *buf = &t->buffer; git_indexer_stream *idx = NULL; - gitno_buffer_setup(t, &buf, buff, sizeof(buff)); - - if (memcmp(buffered, "PACK", strlen("PACK"))) { - giterr_set(GITERR_NET, "The pack doesn't start with the signature"); - return -1; - } - if (git_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)); - if (git_indexer_stream_add(idx, buffered, buffered_size, stats) < 0) - goto on_error; + *bytes = 0; - *bytes = buffered_size; - - do { - if (git_indexer_stream_add(idx, buf.data, buf.offset, stats) < 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(idx, buf, bytes, stats) < 0) goto on_error; - gitno_consume_n(&buf, buf.offset); - if ((recvd = gitno_recv(&buf)) < 0) - goto on_error; + git_indexer_stream_free(idx); + return 0; + } - *bytes += recvd; - } while(recvd > 0); + do { + git_pkt *pkt; + if (recv_pkt(&pkt, buf) < 0) + goto on_error; - if (git_indexer_stream_finalize(idx, stats)) + 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); diff --git a/src/fetch.h b/src/fetch.h index a7f126520b2..87bb43b079d 100644 --- a/src/fetch.h +++ b/src/fetch.h @@ -12,8 +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_transport *t, - git_repository *repo, 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_setup_walk(git_revwalk **out, git_repository *repo); #endif diff --git a/src/filebuf.c b/src/filebuf.c index 876f8e3e7ae..b9b908c8da1 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -50,6 +50,7 @@ static int lock_file(git_filebuf *file, int flags) if (flags & GIT_FILEBUF_FORCE) p_unlink(file->path_lock); else { + giterr_clear(); /* actual OS error code just confuses */ giterr_set(GITERR_OS, "Failed to lock file '%s' for writing", file->path_lock); return -1; @@ -72,7 +73,7 @@ static int lock_file(git_filebuf *file, int flags) if ((flags & GIT_FILEBUF_APPEND) && git_path_exists(file->path_original) == true) { git_file source; char buffer[2048]; - size_t read_bytes; + ssize_t read_bytes; source = p_open(file->path_original, O_RDONLY); if (source < 0) { @@ -82,13 +83,18 @@ static int lock_file(git_filebuf *file, int flags) return -1; } - while ((read_bytes = p_read(source, buffer, 2048)) > 0) { + 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); } p_close(source); + + if (read_bytes < 0) { + giterr_set(GITERR_OS, "Failed to read file '%s'", file->path_original); + return -1; + } } return 0; @@ -319,10 +325,15 @@ int git_filebuf_commit(git_filebuf *file, mode_t mode) if (verify_last_error(file) < 0) goto on_error; - p_close(file->fd); - file->fd = -1; file->fd_is_open = false; + if (p_close(file->fd) < 0) { + giterr_set(GITERR_OS, "Failed to close file at '%s'", file->path_lock); + goto on_error; + } + + file->fd = -1; + if (p_chmod(file->path_lock, mode)) { giterr_set(GITERR_OS, "Failed to set attributes for file at '%s'", file->path_lock); goto on_error; diff --git a/src/fileops.c b/src/fileops.c index 95a65893c72..8ccf063d5d6 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -10,19 +10,8 @@ int git_futils_mkpath2file(const char *file_path, const mode_t mode) { - int result = 0; - git_buf target_folder = GIT_BUF_INIT; - - if (git_path_dirname_r(&target_folder, file_path) < 0) - return -1; - - /* Does the containing folder exist? */ - if (git_path_isdir(target_folder.ptr) == false) - /* Let's create the tree structure */ - result = git_futils_mkdir_r(target_folder.ptr, NULL, mode); - - git_buf_free(&target_folder); - return result; + return git_futils_mkdir( + file_path, NULL, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST); } int git_futils_mktmp(git_buf *path_out, const char *filename) @@ -65,11 +54,10 @@ int git_futils_creat_locked(const char *path, const mode_t mode) int fd; #ifdef GIT_WIN32 - wchar_t* buf; + wchar_t buf[GIT_WIN_PATH]; - buf = gitwin_to_utf16(path); + git__utf8_to_16(buf, GIT_WIN_PATH, path); fd = _wopen(buf, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); - git__free(buf); #else fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY | O_EXCL, mode); #endif @@ -94,7 +82,7 @@ int git_futils_open_ro(const char *path) { int fd = p_open(path, O_RDONLY); if (fd < 0) { - if (errno == ENOENT) + if (errno == ENOENT || errno == ENOTDIR) fd = GIT_ENOTFOUND; giterr_set(GITERR_OS, "Failed to open '%s'", path); } @@ -127,10 +115,33 @@ mode_t git_futils_canonical_mode(mode_t raw_mode) return 0; } -int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, int *updated) +int git_futils_readbuffer_fd(git_buf *buf, git_file fd, size_t len) +{ + ssize_t read_size; + + git_buf_clear(buf); + + if (git_buf_grow(buf, len + 1) < 0) + return -1; + + /* p_read loops internally to read len bytes */ + read_size = p_read(fd, buf->ptr, len); + + if (read_size != (ssize_t)len) { + giterr_set(GITERR_OS, "Failed to read descriptor"); + return -1; + } + + buf->ptr[read_size] = '\0'; + buf->size = read_size; + + return 0; +} + +int git_futils_readbuffer_updated( + git_buf *buf, const char *path, time_t *mtime, int *updated) { git_file fd; - size_t len; struct stat st; assert(buf && path && *path); @@ -159,30 +170,11 @@ int git_futils_readbuffer_updated(git_buf *buf, const char *path, time_t *mtime, if (mtime != NULL) *mtime = st.st_mtime; - len = (size_t) st.st_size; - - git_buf_clear(buf); - - if (git_buf_grow(buf, len + 1) < 0) { + if (git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size) < 0) { p_close(fd); return -1; } - buf->ptr[len] = '\0'; - - while (len > 0) { - ssize_t read_size = p_read(fd, buf->ptr, len); - - if (read_size < 0) { - p_close(fd); - giterr_set(GITERR_OS, "Failed to read descriptor for '%s'", path); - return -1; - } - - len -= read_size; - buf->size += read_size; - } - p_close(fd); if (updated != NULL) @@ -239,66 +231,92 @@ void git_futils_mmap_free(git_map *out) p_munmap(out); } -int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) +int git_futils_mkdir( + const char *path, + const char *base, + mode_t mode, + uint32_t flags) { git_buf make_path = GIT_BUF_INIT; - size_t start = 0; - char *pp, *sp; - bool failed = false; - - if (base != NULL) { - /* - * when a base is being provided, it is supposed to already exist. - * Therefore, no attempt is being made to recursively create this leading path - * segment. It's just skipped. */ - start = strlen(base); - if (git_buf_joinpath(&make_path, base, path) < 0) - return -1; - } else { - int root_path_offset; + ssize_t root = 0; + char lastch, *tail; - if (git_buf_puts(&make_path, path) < 0) - return -1; + /* build path and find "root" where we should start calling mkdir */ + if (git_path_join_unrooted(&make_path, path, base, &root) < 0) + return -1; - root_path_offset = git_path_root(make_path.ptr); - if (root_path_offset > 0) { - /* - * On Windows, will skip the drive name (eg. C: or D:) - * or the leading part of a network path (eg. //computer_name ) */ - start = root_path_offset; - } + if (make_path.size == 0) { + giterr_set(GITERR_OS, "Attempt to create empty path"); + goto fail; } - pp = make_path.ptr + start; - - while (!failed && (sp = strchr(pp, '/')) != NULL) { - if (sp != pp && git_path_isdir(make_path.ptr) == false) { - *sp = 0; - - /* Do not choke while trying to recreate an existing directory */ - if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) - failed = true; + /* remove trailing slashes on path */ + while (make_path.ptr[make_path.size - 1] == '/') { + make_path.size--; + make_path.ptr[make_path.size] = '\0'; + } - *sp = '/'; + /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST) != 0) + git_buf_rtruncate_at_char(&make_path, '/'); + + /* if we are not supposed to make the whole path, reset root */ + if ((flags & GIT_MKDIR_PATH) == 0) + root = git_buf_rfind(&make_path, '/'); + + /* clip root to make_path length */ + if (root >= (ssize_t)make_path.size) + root = (ssize_t)make_path.size - 1; + if (root < 0) + root = 0; + + tail = & make_path.ptr[root]; + + while (*tail) { + /* advance tail to include next path component */ + while (*tail == '/') + tail++; + while (*tail && *tail != '/') + tail++; + + /* truncate path at next component */ + lastch = *tail; + *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; } - pp = sp + 1; - } + /* chmod if requested */ + if ((flags & GIT_MKDIR_CHMOD_PATH) != 0 || + ((flags & GIT_MKDIR_CHMOD) != 0 && lastch == '\0')) + { + if (p_chmod(make_path.ptr, mode) < 0) { + giterr_set(GITERR_OS, "Failed to set permissions on '%s'", + make_path.ptr); + goto fail; + } + } - if (*pp != '\0' && !failed) { - if (p_mkdir(make_path.ptr, mode) < 0 && errno != EEXIST) - failed = true; + *tail = lastch; } git_buf_free(&make_path); + return 0; - if (failed) { - giterr_set(GITERR_OS, - "Failed to create directory structure at '%s'", path); - return -1; - } +fail: + git_buf_free(&make_path); + return -1; +} - return 0; +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) @@ -367,16 +385,16 @@ static int win32_expand_path(struct win32_path *s_root, const wchar_t *templ) static int win32_find_file(git_buf *path, const struct win32_path *root, const char *filename) { - int error = 0; - size_t len; + size_t len, alloc_len; wchar_t *file_utf16 = NULL; - char *file_utf8 = NULL; + char file_utf8[GIT_PATH_MAX]; if (!root || !filename || (len = strlen(filename)) == 0) return GIT_ENOTFOUND; /* allocate space for wchar_t path to file */ - file_utf16 = git__calloc(root->len + len + 2, sizeof(wchar_t)); + alloc_len = root->len + len + 2; + file_utf16 = git__calloc(alloc_len, sizeof(wchar_t)); GITERR_CHECK_ALLOC(file_utf16); /* append root + '\\' + filename as wchar_t */ @@ -385,29 +403,20 @@ static int win32_find_file(git_buf *path, const struct win32_path *root, const c if (*filename == '/' || *filename == '\\') filename++; - if (gitwin_append_utf16(file_utf16 + root->len - 1, filename, len + 1) != - (int)len + 1) { - error = -1; - goto cleanup; - } + git__utf8_to_16(file_utf16 + root->len - 1, alloc_len, filename); /* check access */ if (_waccess(file_utf16, F_OK) < 0) { - error = GIT_ENOTFOUND; - goto cleanup; + git__free(file_utf16); + return GIT_ENOTFOUND; } - /* convert to utf8 */ - if ((file_utf8 = gitwin_from_utf16(file_utf16)) == NULL) - error = -1; - else { - git_path_mkposix(file_utf8); - git_buf_attach(path, file_utf8, 0); - } + git__utf16_to_8(file_utf8, file_utf16); + git_path_mkposix(file_utf8); + git_buf_sets(path, file_utf8); -cleanup: git__free(file_utf16); - return error; + return 0; } #endif @@ -424,6 +433,7 @@ int git_futils_find_system_file(git_buf *path, const char *filename) } if (win32_find_file(path, &root, filename) < 0) { + giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); git_buf_clear(path); return GIT_ENOTFOUND; } @@ -438,6 +448,7 @@ int git_futils_find_system_file(git_buf *path, const char *filename) return 0; git_buf_clear(path); + giterr_set(GITERR_OS, "The system file '%s' doesn't exist", filename); return GIT_ENOTFOUND; #endif } @@ -455,6 +466,7 @@ int git_futils_find_global_file(git_buf *path, const char *filename) } if (win32_find_file(path, &root, filename) < 0) { + giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); git_buf_clear(path); return GIT_ENOTFOUND; } @@ -473,6 +485,7 @@ int git_futils_find_global_file(git_buf *path, const char *filename) return -1; if (git_path_exists(path->ptr) == false) { + giterr_set(GITERR_OS, "The global file '%s' doesn't exist", filename); git_buf_clear(path); return GIT_ENOTFOUND; } @@ -480,3 +493,208 @@ int git_futils_find_global_file(git_buf *path, const char *filename) return 0; #endif } + +int git_futils_fake_symlink(const char *old, const char *new) +{ + int retcode = GIT_ERROR; + int fd = git_futils_creat_withpath(new, 0755, 0644); + if (fd >= 0) { + retcode = p_write(fd, old, strlen(old)); + p_close(fd); + } + return retcode; +} + +static int cp_by_fd(int ifd, int ofd, bool close_fd_when_done) +{ + int error = 0; + char buffer[4096]; + ssize_t len = 0; + + while (!error && (len = p_read(ifd, buffer, sizeof(buffer))) > 0) + /* p_write() does not have the same semantics as write(). It loops + * internally and will return 0 when it has completed writing. + */ + error = p_write(ofd, buffer, len); + + if (len < 0) { + giterr_set(GITERR_OS, "Read error while copying file"); + error = (int)len; + } + + if (close_fd_when_done) { + p_close(ifd); + p_close(ofd); + } + + return error; +} + +int git_futils_cp(const char *from, const char *to, mode_t filemode) +{ + int ifd, ofd; + + if ((ifd = git_futils_open_ro(from)) < 0) + return ifd; + + if ((ofd = p_open(to, O_WRONLY | O_CREAT | O_EXCL, filemode)) < 0) { + if (errno == ENOENT || errno == ENOTDIR) + ofd = GIT_ENOTFOUND; + giterr_set(GITERR_OS, "Failed to open '%s' for writing", to); + p_close(ifd); + return ofd; + } + + return cp_by_fd(ifd, ofd, true); +} + +static int cp_link(const char *from, const char *to, size_t link_size) +{ + int error = 0; + ssize_t read_len; + char *link_data = git__malloc(link_size + 1); + GITERR_CHECK_ALLOC(link_data); + + read_len = p_readlink(from, link_data, link_size); + if (read_len != (ssize_t)link_size) { + giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", from); + error = -1; + } + else { + link_data[read_len] = '\0'; + + if (p_symlink(link_data, to) < 0) { + giterr_set(GITERR_OS, "Could not symlink '%s' as '%s'", + link_data, to); + error = -1; + } + } + + git__free(link_data); + return error; +} + +typedef struct { + const char *to_root; + git_buf to; + ssize_t from_prefix; + uint32_t flags; + uint32_t mkdir_flags; + mode_t dirmode; +} cp_r_info; + +static int _cp_r_callback(void *ref, git_buf *from) +{ + cp_r_info *info = ref; + struct stat from_st, to_st; + bool exists = false; + + if ((info->flags & GIT_CPDIR_COPY_DOTFILES) == 0 && + from->ptr[git_path_basename_offset(from)] == '.') + return 0; + + if (git_buf_joinpath( + &info->to, info->to_root, from->ptr + info->from_prefix) < 0) + return -1; + + if (p_lstat(info->to.ptr, &to_st) < 0) { + if (errno != ENOENT && errno != ENOTDIR) { + giterr_set(GITERR_OS, + "Could not access %s while copying files", info->to.ptr); + return -1; + } + } else + exists = true; + + if (git_path_lstat(from->ptr, &from_st) < 0) + return -1; + + if (S_ISDIR(from_st.st_mode)) { + int error = 0; + mode_t oldmode = info->dirmode; + + /* if we are not chmod'ing, then overwrite dirmode */ + if ((info->flags & GIT_CPDIR_CHMOD) == 0) + info->dirmode = from_st.st_mode; + + /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ + if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) + error = git_futils_mkdir( + info->to.ptr, NULL, info->dirmode, info->mkdir_flags); + + /* recurse onto target directory */ + if (!exists || S_ISDIR(to_st.st_mode)) + error = git_path_direach(from, _cp_r_callback, info); + + if (oldmode != 0) + info->dirmode = oldmode; + + return error; + } + + if (exists) { + if ((info->flags & GIT_CPDIR_OVERWRITE) == 0) + return 0; + + if (p_unlink(info->to.ptr) < 0) { + giterr_set(GITERR_OS, "Cannot overwrite existing file '%s'", + info->to.ptr); + return -1; + } + } + + /* Done if this isn't a regular file or a symlink */ + if (!S_ISREG(from_st.st_mode) && + (!S_ISLNK(from_st.st_mode) || + (info->flags & GIT_CPDIR_COPY_SYMLINKS) == 0)) + return 0; + + /* Make container directory on demand if needed */ + if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && + git_futils_mkdir( + info->to.ptr, NULL, info->dirmode, info->mkdir_flags) < 0) + return -1; + + /* make symlink or regular file */ + if (S_ISLNK(from_st.st_mode)) + return cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + else + return git_futils_cp(from->ptr, info->to.ptr, from_st.st_mode); +} + +int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode) +{ + int error; + git_buf path = GIT_BUF_INIT; + cp_r_info info; + + if (git_buf_sets(&path, from) < 0) + return -1; + + info.to_root = to; + info.flags = flags; + info.dirmode = dirmode; + info.from_prefix = path.size; + git_buf_init(&info.to, 0); + + /* precalculate mkdir flags */ + if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { + info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; + if ((flags & GIT_CPDIR_CHMOD) != 0) + info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; + } else { + info.mkdir_flags = + ((flags & GIT_CPDIR_CHMOD) != 0) ? GIT_MKDIR_CHMOD : 0; + } + + error = _cp_r_callback(&info, &path); + + git_buf_free(&path); + git_buf_free(&info.to); + + return error; +} diff --git a/src/fileops.h b/src/fileops.h index b0c5779e5df..d2944f4603b 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -19,6 +19,7 @@ */ 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_fd(git_buf *obj, git_file fd, size_t len); /** * File utils @@ -50,11 +51,46 @@ extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmo /** * Create a path recursively * - * If a base parameter is being passed, it's expected to be valued with a path pointing to an already - * exisiting directory. + * If a base parameter is being passed, it's expected to be valued with a + * path pointing to an already existing directory. */ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); +/** + * Flags to pass to `git_futils_mkdir`. + * + * * GIT_MKDIR_EXCL is "exclusive" - i.e. generate an error if dir exists. + * * GIT_MKDIR_PATH says to make all components in the path. + * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation + * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path + * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path + * + * Note that the chmod options will be executed even if the directory already + * exists, unless GIT_MKDIR_EXCL is given. + */ +typedef enum { + GIT_MKDIR_EXCL = 1, + GIT_MKDIR_PATH = 2, + GIT_MKDIR_CHMOD = 4, + GIT_MKDIR_CHMOD_PATH = 8, + GIT_MKDIR_SKIP_LAST = 16 +} git_futils_mkdir_flags; + +/** + * Create a directory or entire path. + * + * This makes a directory (and the entire path leading up to it if requested), + * and optionally chmods the directory immediately after (or each part of the + * path if requested). + * + * @param path The path to create. + * @param base Root for relative path. These directories will never be made. + * @param mode The mode to use for created directories. + * @param flags Combination of the mkdir flags above. + * @return 0 on success, else error code + */ +extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags); + /** * Create all the folders required to contain * the full path of a file @@ -94,6 +130,45 @@ extern int git_futils_mktmp(git_buf *path_out, const char *filename); */ extern int git_futils_mv_withpath(const char *from, const char *to, const mode_t dirmode); +/** + * Copy a file + * + * The filemode will be used for the newly created file. + */ +extern int git_futils_cp( + const char *from, + const char *to, + mode_t filemode); + +/** + * Flags that can be passed to `git_futils_cp_r`. + */ +typedef enum { + GIT_CPDIR_CREATE_EMPTY_DIRS = 1, + GIT_CPDIR_COPY_SYMLINKS = 2, + GIT_CPDIR_COPY_DOTFILES = 4, + GIT_CPDIR_OVERWRITE = 8, + GIT_CPDIR_CHMOD = 16 +} git_futils_cpdir_flags; + +/** + * Copy a directory tree. + * + * This copies directories and files from one root to another. You can + * pass a combinationof GIT_CPDIR flags as defined above. + * + * If you pass the CHMOD flag, then the dirmode will be applied to all + * directories that are created during the copy, overiding the natural + * permissions. If you do not pass the CHMOD flag, then the dirmode + * will actually be copied from the source files and the `dirmode` arg + * will be ignored. + */ +extern int git_futils_cp_r( + const char *from, + const char *to, + uint32_t flags, + mode_t dirmode); + /** * Open a file readonly and set error if needed. */ @@ -179,4 +254,14 @@ extern int git_futils_find_global_file(git_buf *path, const char *filename); */ extern int git_futils_find_system_file(git_buf *path, const char *filename); + +/** + * Create a "fake" symlink (text file containing the target path). + * + * @param new symlink file to be created + * @param old original symlink target + * @return 0 on success, -1 on error + */ +extern int git_futils_fake_symlink(const char *new, const char *old); + #endif /* INCLUDE_fileops_h__ */ diff --git a/src/filter.c b/src/filter.c index 8fa3eb684c2..28a05235b1d 100644 --- a/src/filter.c +++ b/src/filter.c @@ -11,6 +11,7 @@ #include "filter.h" #include "repository.h" #include "git2/config.h" +#include "blob.h" /* Tweaked from Core Git. I wonder what we could use this for... */ void git_text_gather_stats(git_text_stats *stats, const git_buf *text) @@ -95,8 +96,9 @@ int git_filters_load(git_vector *filters, git_repository *repo, const char *path if (error < 0) return error; } else { - giterr_set(GITERR_INVALID, "Worktree filters are not implemented yet"); - return -1; + error = git_filter_add__crlf_to_workdir(filters, repo, path); + if (error < 0) + return error; } return (int)filters->length; @@ -162,4 +164,3 @@ int git_filters_apply(git_buf *dest, git_buf *source, git_vector *filters) return 0; } - diff --git a/src/filter.h b/src/filter.h index 66e370aef5a..b9beb49427f 100644 --- a/src/filter.h +++ b/src/filter.h @@ -96,6 +96,9 @@ extern void git_filters_free(git_vector *filters); /* Strip CRLF, from Worktree to ODB */ extern int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path); +/* Add CRLF, from ODB to worktree */ +extern int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path); + /* * PLAINTEXT API diff --git a/src/global.c b/src/global.c index 368c6c66433..691f0d4f689 100644 --- a/src/global.c +++ b/src/global.c @@ -9,6 +9,9 @@ #include "git2/threads.h" #include "thread-utils.h" + +git_mutex git__mwindow_mutex; + /** * Handle the global state with TLS * @@ -47,12 +50,14 @@ void git_threads_init(void) _tls_index = TlsAlloc(); _tls_init = 1; + git_mutex_init(&git__mwindow_mutex); } void git_threads_shutdown(void) { TlsFree(_tls_index); _tls_init = 0; + git_mutex_free(&git__mwindow_mutex); } git_global_st *git__global_state(void) diff --git a/src/global.h b/src/global.h index 2b525ce07fe..0ad41ee63e5 100644 --- a/src/global.h +++ b/src/global.h @@ -10,18 +10,14 @@ #include "mwindow.h" typedef struct { - struct { - char last[1024]; - } error; - git_error *last_error; git_error error_t; - - git_mwindow_ctl mem_ctl; } git_global_st; git_global_st *git__global_state(void); +extern git_mutex git__mwindow_mutex; + #define GIT_GLOBAL (git__global_state()) #endif diff --git a/src/ignore.c b/src/ignore.c index f2d08f59e45..3c2f19ab9f7 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -1,3 +1,4 @@ +#include "git2/ignore.h" #include "ignore.h" #include "path.h" @@ -156,7 +157,7 @@ void git_ignore__free(git_ignores *ignores) static bool ignore_lookup_in_rules( git_vector *rules, git_attr_path *path, int *ignored) { - unsigned int j; + size_t j; git_attr_fnmatch *match; git_vector_rforeach(rules, j, match) { @@ -203,3 +204,55 @@ int git_ignore__lookup( git_attr_path__free(&path); return 0; } + +static int get_internal_ignores(git_attr_file **ign, git_repository *repo) +{ + int error; + + if (!(error = git_attr_cache__init(repo))) + error = git_attr_cache__internal_file(repo, GIT_IGNORE_INTERNAL, ign); + + return error; +} + +int git_ignore_add_rule( + git_repository *repo, + const char *rules) +{ + int error; + git_attr_file *ign_internal; + + if (!(error = get_internal_ignores(&ign_internal, repo))) + error = parse_ignore_file(repo, rules, ign_internal); + + return error; +} + +int git_ignore_clear_internal_rules( + git_repository *repo) +{ + int error; + git_attr_file *ign_internal; + + if (!(error = get_internal_ignores(&ign_internal, repo))) + git_attr_file__clear_rules(ign_internal); + + return error; +} + +int git_ignore_path_is_ignored( + int *ignored, + git_repository *repo, + const char *path) +{ + int error; + git_ignores ignores; + + if (git_ignore__for_path(repo, path, &ignores) < 0) + return -1; + + error = git_ignore__lookup(&ignores, path, ignored); + git_ignore__free(&ignores); + return error; +} + diff --git a/src/index.c b/src/index.c index 89d479870f5..3a92c360bfe 100644 --- a/src/index.c +++ b/src/index.c @@ -14,6 +14,7 @@ #include "tree-cache.h" #include "hash.h" #include "git2/odb.h" +#include "git2/oid.h" #include "git2/blob.h" #include "git2/config.h" @@ -246,7 +247,7 @@ int git_index_set_caps(git_index *index, unsigned int caps) if (git_config_get_bool(&val, cfg, "core.filemode") == 0) index->distrust_filemode = (val == 0); if (git_config_get_bool(&val, cfg, "core.symlinks") == 0) - index->no_symlinks = (val != 0); + index->no_symlinks = (val == 0); } else { index->ignore_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0); @@ -329,16 +330,16 @@ int git_index_write(git_index *index) unsigned int git_index_entrycount(git_index *index) { assert(index); - return index->entries.length; + return (unsigned int)index->entries.length; } unsigned int git_index_entrycount_unmerged(git_index *index) { assert(index); - return index->unmerged.length; + return (unsigned int)index->unmerged.length; } -git_index_entry *git_index_get(git_index *index, unsigned int n) +git_index_entry *git_index_get(git_index *index, size_t n) { git_vector_sort(&index->entries); return git_vector_get(&index->entries, n); @@ -584,7 +585,7 @@ const git_index_entry_unmerged *git_index_get_unmerged_bypath( } const git_index_entry_unmerged *git_index_get_unmerged_byindex( - git_index *index, unsigned int n) + git_index *index, size_t n) { assert(index); return git_vector_get(&index->unmerged, n); @@ -963,7 +964,7 @@ static int write_index(git_index *index, git_filebuf *file) header.signature = htonl(INDEX_HEADER_SIG); header.version = htonl(is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER); - header.entry_count = htonl(index->entries.length); + header.entry_count = htonl((uint32_t)index->entries.length); if (git_filebuf_write(file, &header, sizeof(struct index_header)) < 0) return -1; @@ -985,12 +986,19 @@ int git_index_entry_stage(const git_index_entry *entry) return (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT; } +typedef struct read_tree_data { + git_index *index; + git_indexer_stats *stats; +} read_tree_data; + static int read_tree_cb(const char *root, const git_tree_entry *tentry, void *data) { - git_index *index = data; + read_tree_data *rtd = data; git_index_entry *entry = NULL; git_buf path = GIT_BUF_INIT; + rtd->stats->total++; + if (git_tree_entry__is_tree(tentry)) return 0; @@ -1005,7 +1013,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(index, entry, 0) < 0) { + if (index_insert(rtd->index, entry, 0) < 0) { index_entry_free(entry); return -1; } @@ -1013,9 +1021,16 @@ 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) +int git_index_read_tree(git_index *index, git_tree *tree, git_indexer_stats *stats) { + git_indexer_stats dummy_stats; + read_tree_data rtd = {index, NULL}; + + if (!stats) stats = &dummy_stats; + stats->total = 0; + rtd.stats = stats; + git_index_clear(index); - return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, index); + return git_tree_walk(tree, read_tree_cb, GIT_TREEWALK_POST, &rtd); } diff --git a/src/indexer.c b/src/indexer.c index 1f0ca82a201..85ffb161f1a 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -169,29 +169,14 @@ int git_indexer_stream_new(git_indexer_stream **out, const char *prefix) } /* Try to store the delta so we can try to resolve it later */ -static int store_delta(git_indexer_stream *idx) +static int store_delta(git_indexer_stream *idx, git_off_t entry_start, size_t entry_size, git_otype type) { - git_otype type; git_mwindow *w = NULL; - git_mwindow_file *mwf = &idx->pack->mwf; - git_off_t entry_start = idx->off; struct delta_info *delta; - size_t entry_size; git_rawobj obj; int error; - /* - * ref-delta objects can refer to object that we haven't - * found yet, so give it another opportunity - */ - if (git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off) < 0) - return -1; - - git_mwindow_close(&w); - - /* If it's not a delta, mark it as failure, we can't do anything with it */ - if (type != GIT_OBJ_REF_DELTA && type != GIT_OBJ_OFS_DELTA) - return -1; + assert(type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA); if (type == GIT_OBJ_REF_DELTA) { idx->off += GIT_OID_RAWSZ; @@ -339,8 +324,8 @@ 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->processed = 0; } /* Now that we have data in the pack, let's try to parse it */ @@ -350,33 +335,52 @@ int git_indexer_stream_add(git_indexer_stream *idx, const void *data, size_t siz while (processed < idx->nr_objects) { git_rawobj obj; git_off_t entry_start = idx->off; + size_t entry_size; + git_otype type; + git_mwindow *w = NULL; if (idx->pack->mwf.size <= idx->off + 20) return 0; - error = git_packfile_unpack(&obj, idx->pack, &idx->off); + error = git_packfile_unpack_header(&entry_size, &type, mwf, &w, &idx->off); if (error == GIT_EBUFS) { idx->off = entry_start; return 0; } + if (error < 0) + return -1; - if (error < 0) { - idx->off = entry_start; - error = store_delta(idx); + git_mwindow_close(&w); - if (error == GIT_EBUFS) + if (type == GIT_OBJ_REF_DELTA || type == GIT_OBJ_OFS_DELTA) { + error = store_delta(idx, entry_start, entry_size, type); + if (error == GIT_EBUFS) { + idx->off = entry_start; return 0; + } if (error < 0) return error; + + stats->received++; continue; } + idx->off = entry_start; + error = git_packfile_unpack(&obj, idx->pack, &idx->off); + if (error == GIT_EBUFS) { + idx->off = entry_start; + return 0; + } + if (error < 0) + return -1; + if (hash_and_save(idx, &obj, entry_start) < 0) goto on_error; git__free(obj.data); stats->processed = (unsigned int)++processed; + stats->received++; } return 0; @@ -575,9 +579,11 @@ void git_indexer_stream_free(git_indexer_stream *idx) git_vector_foreach(&idx->objects, i, e) git__free(e); git_vector_free(&idx->objects); - git_vector_foreach(&idx->pack->cache, i, pe) - git__free(pe); - git_vector_free(&idx->pack->cache); + if (idx->pack) { + git_vector_foreach(&idx->pack->cache, i, pe) + git__free(pe); + git_vector_free(&idx->pack->cache); + } git_vector_foreach(&idx->deltas, i, delta) git__free(delta); git_vector_free(&idx->deltas); diff --git a/src/iterator.c b/src/iterator.c index 819b0e22a6b..e30e112203d 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -525,7 +525,9 @@ static int workdir_iterator__advance( while ((wf = wi->stack) != NULL) { next = git_vector_get(&wf->entries, ++wf->index); if (next != NULL) { - if (strcmp(next->path, DOT_GIT "/") == 0) + /* match git's behavior of ignoring anything named ".git" */ + if (strcmp(next->path, DOT_GIT "/") == 0 || + strcmp(next->path, DOT_GIT) == 0) continue; /* else found a good entry */ break; @@ -607,8 +609,8 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) wi->entry.path = ps->path; - /* skip over .git directory */ - if (strcmp(ps->path, DOT_GIT "/") == 0) + /* skip over .git entry */ + if (strcmp(ps->path, DOT_GIT "/") == 0 || strcmp(ps->path, DOT_GIT) == 0) return workdir_iterator__advance((git_iterator *)wi, NULL); /* if there is an error processing the entry, treat as ignored */ @@ -629,15 +631,10 @@ static int workdir_iterator__update_entry(workdir_iterator *wi) /* detect submodules */ if (S_ISDIR(wi->entry.mode)) { - bool is_submodule = git_path_contains(&wi->path, DOT_GIT); - - /* if there is no .git, still check submodules data */ - if (!is_submodule) { - int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path); - is_submodule = (res == 0); - if (res == GIT_ENOTFOUND) - giterr_clear(); - } + int res = git_submodule_lookup(NULL, wi->repo, wi->entry.path); + bool is_submodule = (res == 0); + if (res == GIT_ENOTFOUND) + giterr_clear(); /* if submodule, mark as GITLINK and remove trailing slash */ if (is_submodule) { @@ -662,11 +659,8 @@ int git_iterator_for_workdir_range( assert(iter && repo); - if (git_repository_is_bare(repo)) { - giterr_set(GITERR_INVALID, - "Cannot scan working directory for bare repo"); - return -1; - } + if ((error = git_repository__ensure_not_bare(repo, "scan working directory")) < 0) + return error; ITERATOR_BASE_INIT(wi, workdir, WORKDIR); diff --git a/src/khash.h b/src/khash.h index bd67fe1f72a..242204464aa 100644 --- a/src/khash.h +++ b/src/khash.h @@ -131,7 +131,9 @@ typedef unsigned long long khint64_t; #endif #ifdef _MSC_VER -#define inline __inline +#define kh_inline __inline +#else +#define kh_inline inline #endif typedef khint32_t khint_t; @@ -345,7 +347,7 @@ static const double __ac_HASH_UPPER = 0.77; __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) #define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ - KHASH_INIT2(name, static inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) /* --- BEGIN OF HASH FUNCTIONS --- */ @@ -374,7 +376,7 @@ static const double __ac_HASH_UPPER = 0.77; @param s Pointer to a null terminated string @return The hash value */ -static inline khint_t __ac_X31_hash_string(const char *s) +static kh_inline khint_t __ac_X31_hash_string(const char *s) { khint_t h = (khint_t)*s; if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; @@ -391,7 +393,7 @@ static inline khint_t __ac_X31_hash_string(const char *s) */ #define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) -static inline khint_t __ac_Wang_hash(khint_t key) +static kh_inline khint_t __ac_Wang_hash(khint_t key) { key += ~(key << 15); key ^= (key >> 10); diff --git a/src/message.c b/src/message.c index a4aadb28f70..791b694554e 100644 --- a/src/message.c +++ b/src/message.c @@ -62,21 +62,25 @@ int git_message__prettify(git_buf *message_out, const char *message, int strip_c int git_message_prettify(char *message_out, size_t buffer_size, const char *message, int strip_comments) { git_buf buf = GIT_BUF_INIT; + ssize_t out_size = -1; - if (strlen(message) + 1 > buffer_size) { /* We have to account for a potentially missing \n */ + if (message_out && buffer_size) + *message_out = '\0'; + + if (git_message__prettify(&buf, message, strip_comments) < 0) + goto done; + + if (message_out && buf.size + 1 > buffer_size) { /* +1 for NUL byte */ giterr_set(GITERR_INVALID, "Buffer too short to hold the cleaned message"); - return -1; + goto done; } - *message_out = '\0'; + if (message_out) + git_buf_copy_cstr(message_out, buffer_size, &buf); - if (git_message__prettify(&buf, message, strip_comments) < 0) { - git_buf_free(&buf); - return -1; - } + out_size = buf.size + 1; - git_buf_copy_cstr(message_out, buffer_size, &buf); +done: git_buf_free(&buf); - - return 0; + return (int)out_size; } diff --git a/src/mwindow.c b/src/mwindow.c index 1a5446b9ccd..4da5badb626 100644 --- a/src/mwindow.c +++ b/src/mwindow.c @@ -32,14 +32,20 @@ static struct { DEFAULT_MAPPED_LIMIT, }; +/* Whenever you want to read or modify this, grab git__mwindow_mutex */ +static git_mwindow_ctl mem_ctl; + /* * Free all the windows in a sequence, typically because we're done * with the file */ void git_mwindow_free_all(git_mwindow_file *mwf) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; unsigned int i; + + git_mutex_lock(&git__mwindow_mutex); + /* * Remove these windows from the global list */ @@ -67,6 +73,8 @@ void git_mwindow_free_all(git_mwindow_file *mwf) mwf->windows = w->next; git__free(w); } + + git_mutex_unlock(&git__mwindow_mutex); } /* @@ -82,7 +90,7 @@ int git_mwindow_contains(git_mwindow *win, git_off_t offset) /* * Find the least-recently-used window in a file */ -void git_mwindow_scan_lru( +static void git_mwindow_scan_lru( git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l) @@ -107,11 +115,12 @@ void git_mwindow_scan_lru( /* * Close the least recently used window. You should check to see if - * the file descriptors need closing from time to time. + * the file descriptors need closing from time to time. Called under + * lock from new_window. */ static int git_mwindow_close_lru(git_mwindow_file *mwf) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; unsigned int i; git_mwindow *lru_w = NULL, *lru_l = NULL, **list = &mwf->windows; @@ -146,13 +155,14 @@ static int git_mwindow_close_lru(git_mwindow_file *mwf) return 0; } +/* This gets called under lock from git_mwindow_open */ static git_mwindow *new_window( git_mwindow_file *mwf, git_file fd, git_off_t size, git_off_t offset) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; size_t walign = _mw_options.window_size / 2; git_off_t len; git_mwindow *w; @@ -208,9 +218,10 @@ unsigned char *git_mwindow_open( size_t extra, unsigned int *left) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; git_mwindow *w = *cursor; + git_mutex_lock(&git__mwindow_mutex); if (!w || !(git_mwindow_contains(w, offset) && git_mwindow_contains(w, offset + extra))) { if (w) { w->inuse_cnt--; @@ -228,8 +239,10 @@ unsigned char *git_mwindow_open( */ if (!w) { w = new_window(mwf, mwf->fd, mwf->size, offset); - if (w == NULL) + if (w == NULL) { + git_mutex_unlock(&git__mwindow_mutex); return NULL; + } w->next = mwf->windows; mwf->windows = w; } @@ -247,32 +260,43 @@ unsigned char *git_mwindow_open( if (left) *left = (unsigned int)(w->window_map.len - offset); + git_mutex_unlock(&git__mwindow_mutex); return (unsigned char *) w->window_map.data + offset; } int git_mwindow_file_register(git_mwindow_file *mwf) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; + int ret; + git_mutex_lock(&git__mwindow_mutex); if (ctl->windowfiles.length == 0 && - git_vector_init(&ctl->windowfiles, 8, NULL) < 0) + git_vector_init(&ctl->windowfiles, 8, NULL) < 0) { + git_mutex_unlock(&git__mwindow_mutex); return -1; + } + + ret = git_vector_insert(&ctl->windowfiles, mwf); + git_mutex_unlock(&git__mwindow_mutex); - return git_vector_insert(&ctl->windowfiles, mwf); + return ret; } int git_mwindow_file_deregister(git_mwindow_file *mwf) { - git_mwindow_ctl *ctl = &GIT_GLOBAL->mem_ctl; + git_mwindow_ctl *ctl = &mem_ctl; git_mwindow_file *cur; unsigned int i; + git_mutex_lock(&git__mwindow_mutex); git_vector_foreach(&ctl->windowfiles, i, cur) { if (cur == mwf) { git_vector_remove(&ctl->windowfiles, i); + git_mutex_unlock(&git__mwindow_mutex); return 0; } } + git_mutex_unlock(&git__mwindow_mutex); giterr_set(GITERR_ODB, "Failed to find the memory window file to deregister"); return -1; @@ -282,7 +306,9 @@ void git_mwindow_close(git_mwindow **window) { git_mwindow *w = *window; if (w) { + git_mutex_lock(&git__mwindow_mutex); w->inuse_cnt--; + git_mutex_unlock(&git__mwindow_mutex); *window = NULL; } } diff --git a/src/mwindow.h b/src/mwindow.h index d4fd1956905..c5aeaf77b77 100644 --- a/src/mwindow.h +++ b/src/mwindow.h @@ -38,7 +38,6 @@ typedef struct git_mwindow_ctl { int git_mwindow_contains(git_mwindow *win, git_off_t offset); void git_mwindow_free_all(git_mwindow_file *mwf); unsigned char *git_mwindow_open(git_mwindow_file *mwf, git_mwindow **cursor, git_off_t offset, size_t extra, unsigned int *left); -void git_mwindow_scan_lru(git_mwindow_file *mwf, git_mwindow **lru_w, git_mwindow **lru_l); int git_mwindow_file_register(git_mwindow_file *mwf); int git_mwindow_file_deregister(git_mwindow_file *mwf); void git_mwindow_close(git_mwindow **w_cursor); diff --git a/src/netops.c b/src/netops.c index b369e510687..df502e61979 100644 --- a/src/netops.c +++ b/src/netops.c @@ -55,69 +55,114 @@ static void net_set_error(const char *str) static int ssl_set_error(gitno_ssl *ssl, int error) { int err; + unsigned long e; + err = SSL_get_error(ssl->ssl, error); - giterr_set(GITERR_NET, "SSL error: %s", ERR_error_string(err, NULL)); + + assert(err != SSL_ERROR_WANT_READ); + assert(err != SSL_ERROR_WANT_WRITE); + + switch (err) { + case SSL_ERROR_WANT_CONNECT: + case SSL_ERROR_WANT_ACCEPT: + giterr_set(GITERR_NET, "SSL error: connection failure\n"); + break; + case SSL_ERROR_WANT_X509_LOOKUP: + giterr_set(GITERR_NET, "SSL error: x509 error\n"); + break; + case SSL_ERROR_SYSCALL: + e = ERR_get_error(); + if (e > 0) { + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + } else if (error < 0) { + giterr_set(GITERR_OS, "SSL error: syscall failure"); + break; + } + giterr_set(GITERR_NET, "SSL error: received early EOF"); + break; + case SSL_ERROR_SSL: + e = ERR_get_error(); + giterr_set(GITERR_NET, "SSL error: %s", + ERR_error_string(e, NULL)); + break; + case SSL_ERROR_NONE: + case SSL_ERROR_ZERO_RETURN: + default: + giterr_set(GITERR_NET, "SSL error: unknown error"); + break; + } return -1; } #endif -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len) +int gitno_recv(gitno_buffer *buf) { - memset(buf, 0x0, sizeof(gitno_buffer)); - memset(data, 0x0, len); - buf->data = data; - buf->len = len; - buf->offset = 0; - buf->fd = t->socket; -#ifdef GIT_SSL - if (t->encrypt) - buf->ssl = &t->ssl; -#endif + return buf->recv(buf); } #ifdef GIT_SSL -static int ssl_recv(gitno_ssl *ssl, void *data, size_t len) +static int gitno__recv_ssl(gitno_buffer *buf) { int ret; do { - ret = SSL_read(ssl->ssl, data, len); - } while (SSL_get_error(ssl->ssl, ret) == SSL_ERROR_WANT_READ); + 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); - if (ret < 0) - return ssl_set_error(ssl, ret); + if (ret < 0) { + net_set_error("Error receiving socket data"); + return -1; + } + buf->offset += ret; return ret; } #endif -int gitno_recv(gitno_buffer *buf) +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; } +void gitno_buffer_setup_callback( + git_transport *t, + 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->recv = recv; + buf->cb_data = cb_data; +} + +void gitno_buffer_setup(git_transport *t, 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 +#endif + gitno_buffer_setup_callback(t, buf, data, len, gitno__recv, NULL); +} + /* Consume up to ptr and move the rest of the buffer to the beginning */ void gitno_consume(gitno_buffer *buf, const char *ptr) { @@ -147,7 +192,7 @@ int gitno_ssl_teardown(git_transport *t) int ret; #endif - if (!t->encrypt) + if (!t->use_ssl) return 0; #ifdef GIT_SSL @@ -229,6 +274,10 @@ 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) { + 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)) { @@ -277,7 +326,7 @@ static int verify_server_cert(git_transport *t, const char *host) GENERAL_NAMES_free(alts); if (matched == 0) - goto on_error; + goto cert_fail; if (matched == 1) return 0; @@ -345,7 +394,7 @@ static int ssl_setup(git_transport *t, const char *host) 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); + 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); @@ -415,7 +464,7 @@ int gitno_connect(git_transport *t, const char *host, const char *port) t->socket = s; p_freeaddrinfo(info); - if (t->encrypt && ssl_setup(t, host) < 0) + if (t->use_ssl && ssl_setup(t, host) < 0) return -1; return 0; @@ -429,7 +478,7 @@ static int send_ssl(gitno_ssl *ssl, const char *msg, size_t len) while (off < len) { ret = SSL_write(ssl->ssl, msg + off, len - off); - if (ret <= 0) + if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE) return ssl_set_error(ssl, ret); off += ret; @@ -445,7 +494,7 @@ int gitno_send(git_transport *t, const char *msg, size_t len, int flags) size_t off = 0; #ifdef GIT_SSL - if (t->encrypt) + if (t->use_ssl) return send_ssl(&t->ssl, msg, len); #endif diff --git a/src/netops.h b/src/netops.h index 4976f87f8a5..7c53fd0dc86 100644 --- a/src/netops.h +++ b/src/netops.h @@ -8,10 +8,9 @@ #define INCLUDE_netops_h__ #include "posix.h" -#include "transport.h" #include "common.h" -typedef struct gitno_buffer { +struct gitno_buffer { char *data; size_t len; size_t offset; @@ -19,10 +18,14 @@ typedef struct gitno_buffer { #ifdef GIT_SSL struct gitno_ssl *ssl; #endif -} gitno_buffer; + int (*recv)(gitno_buffer *buffer); + void *cb_data; +}; -void gitno_buffer_setup(git_transport *t, gitno_buffer *buf, char *data, unsigned int len); +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); 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); diff --git a/src/notes.c b/src/notes.c index 7813e9985b1..81e4e507326 100644 --- a/src/notes.c +++ b/src/notes.c @@ -33,7 +33,7 @@ static int find_subtree_in_current_level( if (!git__ishex(git_tree_entry_name(entry))) continue; - if (S_ISDIR(git_tree_entry_attributes(entry)) + if (S_ISDIR(git_tree_entry_filemode(entry)) && strlen(git_tree_entry_name(entry)) == 2 && !strncmp(git_tree_entry_name(entry), annotated_object_sha + fanout, 2)) return git_tree_lookup(out, repo, git_tree_entry_id(entry)); @@ -180,7 +180,7 @@ static int manipulate_note_in_tree_r( subtree_name[2] = '\0'; error = tree_write(out, repo, parent, git_tree_id(new), - subtree_name, 0040000); + subtree_name, GIT_FILEMODE_TREE); cleanup: @@ -252,7 +252,13 @@ static int insert_note_in_tree_enotfound_cb(git_tree **out, GIT_UNUSED(current_error); /* No existing fanout at this level, insert in place */ - return tree_write(out, repo, parent, note_oid, annotated_object_sha + fanout, 0100644); + return tree_write( + out, + repo, + parent, + note_oid, + annotated_object_sha + fanout, + GIT_FILEMODE_BLOB); } static int note_write(git_oid *out, @@ -522,13 +528,14 @@ static int process_entry_path( int (*note_cb)(git_note_data *note_data, void *payload), void *payload) { - int i = 0, j = 0, error = -1, len; + int error = -1; + size_t i = 0, j = 0, len; git_buf buf = GIT_BUF_INIT; git_note_data note_data; - if (git_buf_puts(&buf, entry_path) < 0) + if ((error = git_buf_puts(&buf, entry_path)) < 0) goto cleanup; - + len = git_buf_len(&buf); while (i < len) { @@ -536,10 +543,9 @@ static int process_entry_path( i++; continue; } - + if (git__fromhex(buf.ptr[i]) < 0) { /* This is not a note entry */ - error = 0; goto cleanup; } @@ -555,16 +561,17 @@ static int process_entry_path( if (j != GIT_OID_HEXSZ) { /* This is not a note entry */ - error = 0; goto cleanup; } - if (git_oid_fromstr(¬e_data.annotated_object_oid, buf.ptr) < 0) - return -1; + if ((error = git_oid_fromstr( + ¬e_data.annotated_object_oid, buf.ptr)) < 0) + goto cleanup; git_oid_cpy(¬e_data.blob_oid, note_oid); - error = note_cb(¬e_data, payload); + if (note_cb(¬e_data, payload)) + error = GIT_EUSER; cleanup: git_buf_free(&buf); @@ -577,34 +584,27 @@ int git_note_foreach( int (*note_cb)(git_note_data *note_data, void *payload), void *payload) { - int error = -1; + int error; git_iterator *iter = NULL; git_tree *tree = NULL; git_commit *commit = NULL; const git_index_entry *item; - if ((error = retrieve_note_tree_and_commit(&tree, &commit, repo, ¬es_ref)) < 0) - goto cleanup; - - if (git_iterator_for_tree(&iter, repo, tree) < 0) - goto cleanup; - - if (git_iterator_current(iter, &item) < 0) - goto cleanup; + if (!(error = retrieve_note_tree_and_commit( + &tree, &commit, repo, ¬es_ref)) && + !(error = git_iterator_for_tree(&iter, repo, tree))) + error = git_iterator_current(iter, &item); - while (item) { - if (process_entry_path(item->path, &item->oid, note_cb, payload) < 0) - goto cleanup; + while (!error && item) { + error = process_entry_path(item->path, &item->oid, note_cb, payload); - if (git_iterator_advance(iter, &item) < 0) - goto cleanup; + if (!error) + error = git_iterator_advance(iter, &item); } - error = 0; - -cleanup: git_iterator_free(iter); git_tree_free(tree); git_commit_free(commit); + return error; } diff --git a/src/object.c b/src/object.c index 14d64befe62..2e45eb86aae 100644 --- a/src/object.c +++ b/src/object.c @@ -77,11 +77,63 @@ static int create_object(git_object **object_out, git_otype type) return 0; } +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_otype type) +{ + int error; + git_object *object = NULL; + + if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { + giterr_set(GITERR_ODB, "The requested type does not match the type in the ODB"); + return GIT_ENOTFOUND; + } + + type = odb_obj->raw.type; + + if ((error = create_object(&object, type)) < 0) + return error; + + /* Initialize parent object */ + git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); + object->repo = repo; + + switch (type) { + case GIT_OBJ_COMMIT: + error = git_commit__parse((git_commit *)object, odb_obj); + break; + + case GIT_OBJ_TREE: + error = git_tree__parse((git_tree *)object, odb_obj); + break; + + case GIT_OBJ_TAG: + error = git_tag__parse((git_tag *)object, odb_obj); + break; + + case GIT_OBJ_BLOB: + error = git_blob__parse((git_blob *)object, odb_obj); + break; + + default: + break; + } + + if (error < 0) + git_object__free(object); + else + *object_out = git_cache_try_store(&repo->objects, object); + + return error; +} + int git_object_lookup_prefix( git_object **object_out, git_repository *repo, const git_oid *id, - unsigned int len, + size_t len, git_otype type) { git_object *object = NULL; @@ -148,53 +200,11 @@ int git_object_lookup_prefix( if (error < 0) return error; - if (type != GIT_OBJ_ANY && type != odb_obj->raw.type) { - git_odb_object_free(odb_obj); - giterr_set(GITERR_ODB, "The given type does not match the type on the ODB"); - return GIT_ENOTFOUND; - } - - type = odb_obj->raw.type; - - if (create_object(&object, type) < 0) { - git_odb_object_free(odb_obj); - return -1; - } - - /* Initialize parent object */ - git_oid_cpy(&object->cached.oid, &odb_obj->cached.oid); - object->repo = repo; - - switch (type) { - case GIT_OBJ_COMMIT: - error = git_commit__parse((git_commit *)object, odb_obj); - break; - - case GIT_OBJ_TREE: - error = git_tree__parse((git_tree *)object, odb_obj); - break; - - case GIT_OBJ_TAG: - error = git_tag__parse((git_tag *)object, odb_obj); - break; - - case GIT_OBJ_BLOB: - error = git_blob__parse((git_blob *)object, odb_obj); - break; - - default: - break; - } + error = git_object__from_odb_object(object_out, repo, odb_obj, type); git_odb_object_free(odb_obj); - if (error < 0) { - git_object__free(object); - return -1; - } - - *object_out = git_cache_try_store(&repo->objects, object); - return 0; + return error; } int git_object_lookup(git_object **object_out, git_repository *repo, const git_oid *id, git_otype type) { @@ -333,3 +343,73 @@ int git_object__resolve_to_type(git_object **obj, git_otype type) *obj = scan; return error; } + +static int peel_error(int error, const char* msg) +{ + giterr_set(GITERR_INVALID, "The given object cannot be peeled - %s", msg); + return error; +} + +static int dereference_object(git_object **dereferenced, git_object *obj) +{ + git_otype type = git_object_type(obj); + + switch (type) { + case GIT_OBJ_COMMIT: + return git_commit_tree((git_tree **)dereferenced, (git_commit*)obj); + + case GIT_OBJ_TAG: + return git_tag_target(dereferenced, (git_tag*)obj); + + case GIT_OBJ_BLOB: + return peel_error(GIT_ERROR, "cannot dereference blob"); + + case GIT_OBJ_TREE: + return peel_error(GIT_ERROR, "cannot dereference tree"); + + default: + return peel_error(GIT_ENOTFOUND, "unexpected object type encountered"); + } +} + +int git_object_peel( + git_object **peeled, + git_object *object, + git_otype target_type) +{ + git_object *source, *deref = NULL; + + assert(object && peeled); + + if (git_object_type(object) == target_type) + return git_object__dup(peeled, object); + + source = object; + + while (!dereference_object(&deref, source)) { + + if (source != object) + git_object_free(source); + + if (git_object_type(deref) == target_type) { + *peeled = deref; + return 0; + } + + if (target_type == GIT_OBJ_ANY && + git_object_type(deref) != git_object_type(object)) + { + *peeled = deref; + return 0; + } + + source = deref; + deref = NULL; + } + + if (source != object) + git_object_free(source); + + git_object_free(deref); + return -1; +} diff --git a/src/object.h b/src/object.h new file mode 100644 index 00000000000..bc12aad04d9 --- /dev/null +++ b/src/object.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_object_h__ +#define INCLUDE_object_h__ + +/** Base git object for inheritance */ +struct git_object { + git_cached_obj cached; + git_repository *repo; + git_otype type; +}; + +/* fully free the object; internal method, DO NOT EXPORT */ +void git_object__free(void *object); + +GIT_INLINE(int) git_object__dup(git_object **dest, git_object *source) +{ + git_cached_obj_incref(source); + *dest = source; + return 0; +} + +int git_object__from_odb_object( + git_object **object_out, + git_repository *repo, + git_odb_object *odb_obj, + git_otype type); + +int git_object__resolve_to_type(git_object **obj, git_otype type); + +int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); + +void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); + +#endif diff --git a/src/odb.c b/src/odb.c index e0c8fa26255..d1ffff652c5 100644 --- a/src/odb.c +++ b/src/odb.c @@ -12,8 +12,10 @@ #include "hash.h" #include "odb.h" #include "delta-apply.h" +#include "filter.h" #include "git2/odb_backend.h" +#include "git2/oid.h" #define GIT_ALTERNATES_FILE "info/alternates" @@ -113,32 +115,67 @@ 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; + ssize_t read_len = -1; + + 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); ctx = git_hash_new_ctx(); + GITERR_CHECK_ALLOC(ctx); git_hash_update(ctx, hdr, hdr_len); - while (size > 0) { - ssize_t read_len = read(fd, buffer, sizeof(buffer)); - - if (read_len < 0) { - git_hash_free_ctx(ctx); - giterr_set(GITERR_OS, "Error reading file"); - return -1; - } - + while (size > 0 && (read_len = p_read(fd, buffer, sizeof(buffer))) > 0) { git_hash_update(ctx, buffer, read_len); size -= read_len; } + /* If p_read returned an error code, the read obviously failed. + * 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"); + return -1; + } + git_hash_final(out, ctx); git_hash_free_ctx(ctx); return 0; } +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t size, git_otype type, git_vector *filters) +{ + int error; + git_buf raw = GIT_BUF_INIT; + git_buf filtered = GIT_BUF_INIT; + + if (!filters || !filters->length) + return git_odb__hashfd(out, fd, size, type); + + /* size of data is used in header, so we have to read the whole file + * into memory to apply filters before beginning to calculate the hash + */ + + if (!(error = git_futils_readbuffer_fd(&raw, fd, size))) + error = git_filters_apply(&filtered, &raw, filters); + + git_buf_free(&raw); + + if (!error) + error = git_odb_hash(out, filtered.ptr, filtered.size, type); + + git_buf_free(&filtered); + + return error; +} + int git_odb__hashlink(git_oid *out, const char *path) { struct stat st; @@ -159,10 +196,11 @@ int git_odb__hashlink(git_oid *out, const char *path) char *link_data; ssize_t read_len; - link_data = git__malloc((size_t)size); + link_data = git__malloc((size_t)(size + 1)); GITERR_CHECK_ALLOC(link_data); - read_len = p_readlink(path, link_data, (size_t)(size + 1)); + read_len = p_readlink(path, link_data, (size_t)size); + link_data[size] = '\0'; if (read_len != (ssize_t)size) { giterr_set(GITERR_OS, "Failed to read symlink data for '%s'", path); return -1; @@ -170,7 +208,7 @@ int git_odb__hashlink(git_oid *out, const char *path) result = git_odb_hash(out, link_data, (size_t)size, GIT_OBJ_BLOB); git__free(link_data); - } else { + } else { int fd = git_futils_open_ro(path); if (fd < 0) return -1; @@ -483,20 +521,37 @@ int git_odb_exists(git_odb *db, const git_oid *id) } int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) +{ + int error; + git_odb_object *object; + + error = git_odb__read_header_or_object(&object, len_p, type_p, db, id); + + if (object) + git_odb_object_free(object); + + return error; +} + +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_otype *type_p, + git_odb *db, const git_oid *id) { unsigned int i; int error = GIT_ENOTFOUND; git_odb_object *object; - assert(db && id); + assert(db && id && out && len_p && type_p); if ((object = git_cache_get(&db->cache, id)) != NULL) { *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_free(object); + *out = object; return 0; } + *out = NULL; + 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; @@ -517,7 +572,8 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git *len_p = object->raw.len; *type_p = object->raw.type; - git_odb_object_free(object); + *out = object; + return 0; } @@ -553,7 +609,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) } int git_odb_read_prefix( - git_odb_object **out, git_odb *db, const git_oid *short_id, unsigned int len) + git_odb_object **out, git_odb *db, const git_oid *short_id, size_t len) { unsigned int i; int error = GIT_ENOTFOUND; @@ -605,6 +661,21 @@ int git_odb_read_prefix( return 0; } +int git_odb_foreach(git_odb *db, int (*cb)(git_oid *oid, void *data), void *data) +{ + unsigned int i; + backend_internal *internal; + + git_vector_foreach(&db->backends, i, internal) { + git_odb_backend *b = internal->backend; + int error = b->foreach(b, cb, data); + if (error < 0) + return error; + } + + return 0; +} + int git_odb_write( git_oid *oid, git_odb *db, const void *data, size_t len, git_otype type) { @@ -692,6 +763,12 @@ int git_odb_open_rstream(git_odb_stream **stream, git_odb *db, const git_oid *oi return error; } +void * git_odb_backend_malloc(git_odb_backend *backend, size_t len) +{ + GIT_UNUSED(backend); + return git__malloc(len); +} + int git_odb__error_notfound(const char *message, const git_oid *oid) { if (oid != NULL) { diff --git a/src/odb.h b/src/odb.h index 263e4c30b30..e9e33dde8f6 100644 --- a/src/odb.h +++ b/src/odb.h @@ -58,12 +58,19 @@ int git_odb__hashobj(git_oid *id, git_rawobj *obj); int git_odb__hashfd(git_oid *out, git_file fd, size_t size, git_otype type); /* - * Hash a `path`, assuming it could be a POSIX symlink: if the path is a symlink, - * then the raw contents of the symlink will be hashed. Otherwise, this will - * fallback to `git_odb__hashfd`. + * Hash an open file descriptor applying an array of filters + * Acts just like git_odb__hashfd with the addition of filters... + */ +int git_odb__hashfd_filtered( + git_oid *out, git_file fd, size_t len, git_otype type, git_vector *filters); + +/* + * Hash a `path`, assuming it could be a POSIX symlink: if the path is a + * symlink, then the raw contents of the symlink will be hashed. Otherwise, + * this will fallback to `git_odb__hashfd`. * - * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may only - * point to blobs. + * The hash type for this call is always `GIT_OBJ_BLOB` because symlinks may + * only point to blobs. */ int git_odb__hashlink(git_oid *out, const char *path); @@ -77,4 +84,12 @@ int git_odb__error_notfound(const char *message, const git_oid *oid); */ int git_odb__error_ambiguous(const char *message); +/* + * Attempt to read object header or just return whole object if it could + * not be read. + */ +int git_odb__read_header_or_object( + git_odb_object **out, size_t *len_p, git_otype *type_p, + git_odb *db, const git_oid *id); + #endif diff --git a/src/odb_loose.c b/src/odb_loose.c index 989b03ab2be..41121ae1089 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -42,7 +42,7 @@ typedef struct loose_backend { typedef struct { size_t dir_len; unsigned char short_oid[GIT_OID_HEXSZ]; /* hex formatted oid to match */ - unsigned int short_oid_len; + size_t short_oid_len; int found; /* number of matching * objects already found */ unsigned char res_oid[GIT_OID_HEXSZ]; /* hex formatted oid of @@ -502,7 +502,7 @@ static int locate_object_short_oid( git_oid *res_oid, loose_backend *backend, const git_oid *short_oid, - unsigned int len) + size_t len) { char *objects_dir = backend->objects_dir; size_t dir_len = strlen(objects_dir); @@ -629,7 +629,7 @@ static int loose_backend__read_prefix( git_otype *type_p, git_odb_backend *backend, const git_oid *short_oid, - unsigned int len) + size_t len) { int error = 0; @@ -676,6 +676,91 @@ static int loose_backend__exists(git_odb_backend *backend, const git_oid *oid) return !error; } +struct foreach_state { + size_t dir_len; + int (*cb)(git_oid *oid, void *data); + void *data; + int cb_error; +}; + +GIT_INLINE(int) filename_to_oid(git_oid *oid, const char *ptr) +{ + int v, i = 0; + if (strlen(ptr) != 41) + return -1; + + if (ptr[2] != '/') { + return -1; + } + + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i+1]); + if (v < 0) + return -1; + + oid->id[0] = (unsigned char) v; + + ptr += 3; + for (i = 0; i < 38; i += 2) { + v = (git__fromhex(ptr[i]) << 4) | git__fromhex(ptr[i + 1]); + if (v < 0) + return -1; + + oid->id[1 + i/2] = (unsigned char) v; + } + + return 0; +} + +static int foreach_object_dir_cb(void *_state, git_buf *path) +{ + git_oid oid; + struct foreach_state *state = (struct foreach_state *) _state; + + if (filename_to_oid(&oid, path->ptr + state->dir_len) < 0) + return 0; + + if (state->cb(&oid, state->data)) { + state->cb_error = GIT_EUSER; + return -1; + } + + return 0; +} + +static int foreach_cb(void *_state, git_buf *path) +{ + struct foreach_state *state = (struct foreach_state *) _state; + + return git_path_direach(path, foreach_object_dir_cb, state); +} + +static int loose_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *oid, void *data), void *data) +{ + char *objects_dir; + int error; + git_buf buf = GIT_BUF_INIT; + struct foreach_state state; + loose_backend *backend = (loose_backend *) _backend; + + assert(backend && cb); + + objects_dir = backend->objects_dir; + + git_buf_sets(&buf, objects_dir); + git_path_to_dir(&buf); + + memset(&state, 0, sizeof(state)); + state.cb = cb; + state.data = data; + state.dir_len = git_buf_len(&buf); + + error = git_path_direach(&buf, foreach_cb, &state); + + git_buf_free(&buf); + + return state.cb_error ? state.cb_error : error; +} + static int loose_backend__stream_fwrite(git_oid *oid, git_odb_stream *_stream) { loose_writestream *stream = (loose_writestream *)_stream; @@ -845,6 +930,7 @@ int git_odb_backend_loose( backend->parent.read_header = &loose_backend__read_header; backend->parent.writestream = &loose_backend__stream; backend->parent.exists = &loose_backend__exists; + backend->parent.foreach = &loose_backend__foreach; backend->parent.free = &loose_backend__free; *backend_out = (git_odb_backend *)backend; diff --git a/src/odb_pack.c b/src/odb_pack.c index 458f288d981..b4f958b6f41 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -149,7 +149,7 @@ static int pack_entry_find_prefix( struct git_pack_entry *e, struct pack_backend *backend, const git_oid *short_oid, - unsigned int len); + size_t len); @@ -267,19 +267,20 @@ static int pack_entry_find(struct git_pack_entry *e, struct pack_backend *backen { int error; unsigned int i; + struct git_pack_file *last_found = backend->last_found; + + if (last_found && + git_pack_entry_find(e, last_found, oid, GIT_OID_HEXSZ) == 0) + return 0; if ((error = packfile_refresh_all(backend)) < 0) return error; - if (backend->last_found && - git_pack_entry_find(e, backend->last_found, oid, GIT_OID_HEXSZ) == 0) - return 0; - for (i = 0; i < backend->packs.length; ++i) { struct git_pack_file *p; p = git_vector_get(&backend->packs, i); - if (p == backend->last_found) + if (p == last_found) continue; if (git_pack_entry_find(e, p, oid, GIT_OID_HEXSZ) == 0) { @@ -295,17 +296,18 @@ static int pack_entry_find_prefix( struct git_pack_entry *e, struct pack_backend *backend, const git_oid *short_oid, - unsigned int len) + size_t len) { int error; unsigned int i; unsigned found = 0; + struct git_pack_file *last_found = backend->last_found; if ((error = packfile_refresh_all(backend)) < 0) return error; - if (backend->last_found) { - error = git_pack_entry_find(e, backend->last_found, short_oid, len); + if (last_found) { + error = git_pack_entry_find(e, last_found, short_oid, len); if (error == GIT_EAMBIGUOUS) return error; if (!error) @@ -316,7 +318,7 @@ static int pack_entry_find_prefix( struct git_pack_file *p; p = git_vector_get(&backend->packs, i); - if (p == backend->last_found) + if (p == last_found) continue; error = git_pack_entry_find(e, p, short_oid, len); @@ -384,7 +386,7 @@ static int pack_backend__read_prefix( git_otype *type_p, git_odb_backend *backend, const git_oid *short_oid, - unsigned int len) + size_t len) { int error = 0; @@ -420,6 +422,28 @@ static int pack_backend__exists(git_odb_backend *backend, const git_oid *oid) return pack_entry_find(&e, (struct pack_backend *)backend, oid) == 0; } +static int pack_backend__foreach(git_odb_backend *_backend, int (*cb)(git_oid *oid, void *data), void *data) +{ + int error; + struct git_pack_file *p; + struct pack_backend *backend; + unsigned int i; + + assert(_backend && cb); + backend = (struct pack_backend *)_backend; + + /* Make sure we know about the packfiles */ + if ((error = packfile_refresh_all(backend)) < 0) + return error; + + git_vector_foreach(&backend->packs, i, p) { + if ((error = git_pack_foreach_entry(p, cb, data)) < 0) + return error; + } + + return 0; +} + static void pack_backend__free(git_odb_backend *_backend) { struct pack_backend *backend; @@ -439,6 +463,41 @@ static void pack_backend__free(git_odb_backend *_backend) git__free(backend); } +int git_odb_backend_one_pack(git_odb_backend **backend_out, const char *idx) +{ + struct pack_backend *backend = NULL; + struct git_pack_file *packfile = NULL; + + if (git_packfile_check(&packfile, idx) < 0) + return -1; + + backend = git__calloc(1, sizeof(struct pack_backend)); + GITERR_CHECK_ALLOC(backend); + + if (git_vector_init(&backend->packs, 1, NULL) < 0) + goto on_error; + + if (git_vector_insert(&backend->packs, packfile) < 0) + goto on_error; + + backend->parent.read = &pack_backend__read; + backend->parent.read_prefix = &pack_backend__read_prefix; + backend->parent.read_header = NULL; + backend->parent.exists = &pack_backend__exists; + backend->parent.foreach = &pack_backend__foreach; + backend->parent.free = &pack_backend__free; + + *backend_out = (git_odb_backend *)backend; + + return 0; + +on_error: + git_vector_free(&backend->packs); + git__free(backend); + git__free(packfile); + return -1; +} + int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) { struct pack_backend *backend = NULL; @@ -463,6 +522,7 @@ int git_odb_backend_pack(git_odb_backend **backend_out, const char *objects_dir) backend->parent.read_prefix = &pack_backend__read_prefix; backend->parent.read_header = NULL; backend->parent.exists = &pack_backend__exists; + backend->parent.foreach = &pack_backend__foreach; backend->parent.free = &pack_backend__free; *backend_out = (git_odb_backend *)backend; diff --git a/src/oid.c b/src/oid.c index 87756010bb2..127ad611752 100644 --- a/src/oid.c +++ b/src/oid.c @@ -24,9 +24,6 @@ int git_oid_fromstrn(git_oid *out, const char *str, size_t length) size_t p; int v; - if (length < 4) - return oid_error_invalid("input too short"); - if (length > GIT_OID_HEXSZ) length = GIT_OID_HEXSZ; @@ -161,12 +158,7 @@ void git_oid_cpy(git_oid *out, const git_oid *src) memcpy(out->id, src->id, sizeof(out->id)); } -int git_oid_cmp(const git_oid *a, const git_oid *b) -{ - return memcmp(a->id, b->id, sizeof(a->id)); -} - -int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, unsigned int len) +int git_oid_ncmp(const git_oid *oid_a, const git_oid *oid_b, size_t len) { const unsigned char *a = oid_a->id; const unsigned char *b = oid_b->id; diff --git a/src/oidmap.h b/src/oidmap.h index 858268c92cc..1791adb1605 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -28,13 +28,8 @@ GIT_INLINE(khint_t) hash_git_oid(const git_oid *oid) return h; } -GIT_INLINE(int) hash_git_oid_equal(const git_oid *a, const git_oid *b) -{ - return (memcmp(a->id, b->id, sizeof(a->id)) == 0); -} - #define GIT__USE_OIDMAP \ - __KHASH_IMPL(oid, static inline, const git_oid *, void *, 1, hash_git_oid, hash_git_oid_equal) + __KHASH_IMPL(oid, static kh_inline, const git_oid *, void *, 1, hash_git_oid, git_oid_equal) #define git_oidmap_alloc() kh_init(oid) #define git_oidmap_free(h) kh_destroy(oid,h), h = NULL diff --git a/src/pack.c b/src/pack.c index 808ceb70ca9..9346aced61c 100644 --- a/src/pack.c +++ b/src/pack.c @@ -38,7 +38,7 @@ static int pack_entry_find_offset( git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid, - unsigned int len); + size_t len); static int packfile_error(const char *message) { @@ -54,6 +54,10 @@ static int packfile_error(const char *message) static void pack_index_free(struct git_pack_file *p) { + if (p->oids) { + git__free(p->oids); + p->oids = NULL; + } if (p->index_map.data) { git_futils_mmap_free(&p->index_map); p->index_map.data = NULL; @@ -686,12 +690,76 @@ static git_off_t nth_packed_object_offset(const struct git_pack_file *p, uint32_ } } +static int git__memcmp4(const void *a, const void *b) { + return memcmp(a, b, 4); +} + +int git_pack_foreach_entry( + struct git_pack_file *p, + int (*cb)(git_oid *oid, void *data), + void *data) +{ + const unsigned char *index = p->index_map.data, *current; + uint32_t i; + + if (index == NULL) { + int error; + + if ((error = pack_index_open(p)) < 0) + return error; + + assert(p->index_map.data); + + index = p->index_map.data; + } + + if (p->index_version > 1) { + index += 8; + } + + index += 4 * 256; + + if (p->oids == NULL) { + git_vector offsets, oids; + int error; + + if ((error = git_vector_init(&oids, p->num_objects, NULL))) + return error; + + if ((error = git_vector_init(&offsets, p->num_objects, git__memcmp4))) + return error; + + if (p->index_version > 1) { + const unsigned char *off = index + 24 * p->num_objects; + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&off[4 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)&index[5 * (current - off)]); + } else { + for (i = 0; i < p->num_objects; i++) + git_vector_insert(&offsets, (void*)&index[24 * i]); + git_vector_sort(&offsets); + git_vector_foreach(&offsets, i, current) + git_vector_insert(&oids, (void*)¤t[4]); + } + git_vector_free(&offsets); + p->oids = (git_oid **)oids.contents; + } + + for (i = 0; i < p->num_objects; i++) + if (cb(p->oids[i], data)) + return GIT_EUSER; + + return 0; +} + static int pack_entry_find_offset( git_off_t *offset_out, git_oid *found_oid, struct git_pack_file *p, const git_oid *short_oid, - unsigned int len) + size_t len) { const uint32_t *level1_ofs = p->index_map.data; const unsigned char *index = p->index_map.data; @@ -784,7 +852,7 @@ int git_pack_entry_find( struct git_pack_entry *e, struct git_pack_file *p, const git_oid *short_oid, - unsigned int len) + size_t len) { git_off_t offset; git_oid found_oid; diff --git a/src/pack.h b/src/pack.h index cd7a4d2e16f..af87b7cd5ab 100644 --- a/src/pack.h +++ b/src/pack.h @@ -64,6 +64,7 @@ struct git_pack_file { unsigned pack_local:1, pack_keep:1, has_cache:1; git_oid sha1; git_vector cache; + git_oid **oids; /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[GIT_FLEX_ARRAY]; /* more */ @@ -101,6 +102,10 @@ int git_pack_entry_find( struct git_pack_entry *e, struct git_pack_file *p, const git_oid *short_oid, - unsigned int len); + size_t len); +int git_pack_foreach_entry( + struct git_pack_file *p, + int (*cb)(git_oid *oid, void *data), + void *data); #endif diff --git a/src/path.c b/src/path.c index a6574b3de82..09556bd3fbc 100644 --- a/src/path.c +++ b/src/path.c @@ -17,9 +17,7 @@ #include #include -#ifdef GIT_WIN32 #define LOOKS_LIKE_DRIVE_PREFIX(S) (git__isalpha((S)[0]) && (S)[1] == ':') -#endif /* * Based on the Android implementation, BSD licensed. @@ -149,6 +147,20 @@ char *git_path_basename(const char *path) return basename; } +size_t git_path_basename_offset(git_buf *buffer) +{ + ssize_t slash; + + if (!buffer || buffer->size <= 0) + return 0; + + slash = git_buf_rfind_next(buffer, '/'); + + if (slash >= 0 && buffer->ptr[slash] == '/') + return (size_t)(slash + 1); + + return 0; +} const char *git_path_topdir(const char *path) { @@ -172,11 +184,11 @@ int git_path_root(const char *path) { int offset = 0; -#ifdef GIT_WIN32 /* Does the root of the path look like a windows drive ? */ if (LOOKS_LIKE_DRIVE_PREFIX(path)) offset += 2; +#ifdef GIT_WIN32 /* Are we dealing with a windows network path? */ else if ((path[0] == '/' && path[1] == '/') || (path[0] == '\\' && path[1] == '\\')) @@ -195,6 +207,31 @@ int git_path_root(const char *path) return -1; /* Not a real error - signals that path is not rooted */ } +int git_path_join_unrooted( + git_buf *path_out, const char *path, const char *base, ssize_t *root_at) +{ + int error, root; + + assert(path && path_out); + + root = git_path_root(path); + + if (base != NULL && root < 0) { + error = git_buf_joinpath(path_out, base, path); + + if (root_at) + *root_at = (ssize_t)strlen(base); + } + else { + error = git_buf_sets(path_out, path); + + if (root_at) + *root_at = (root < 0) ? 0 : (ssize_t)root; + } + + return error; +} + int git_path_prettify(git_buf *path_out, const char *path, const char *base) { char buf[GIT_PATH_MAX]; @@ -389,6 +426,68 @@ bool git_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +#ifdef GIT_WIN32 + +bool git_path_is_empty_dir(const char *path) +{ + git_buf pathbuf = GIT_BUF_INIT; + HANDLE hFind = INVALID_HANDLE_VALUE; + wchar_t wbuf[GIT_WIN_PATH]; + WIN32_FIND_DATAW ffd; + bool retval = true; + + if (!git_path_isdir(path)) return false; + + git_buf_printf(&pathbuf, "%s\\*", path); + git__utf8_to_16(wbuf, GIT_WIN_PATH, git_buf_cstr(&pathbuf)); + + hFind = FindFirstFileW(wbuf, &ffd); + if (INVALID_HANDLE_VALUE == hFind) { + giterr_set(GITERR_OS, "Couldn't open '%s'", path); + return false; + } + + do { + if (!git_path_is_dot_or_dotdotW(ffd.cFileName)) { + retval = false; + } + } while (FindNextFileW(hFind, &ffd) != 0); + + FindClose(hFind); + git_buf_free(&pathbuf); + return retval; +} + +#else + +bool git_path_is_empty_dir(const char *path) +{ + DIR *dir = NULL; + struct dirent *e; + bool retval = true; + + if (!git_path_isdir(path)) return false; + + dir = opendir(path); + if (!dir) { + giterr_set(GITERR_OS, "Couldn't open '%s'", path); + return false; + } + + while ((e = readdir(dir)) != NULL) { + if (!git_path_is_dot_or_dotdot(e->d_name)) { + giterr_set(GITERR_INVALID, + "'%s' exists and is not an empty directory", path); + retval = false; + break; + } + } + closedir(dir); + + return retval; +} +#endif + int git_path_lstat(const char *path, struct stat *st) { int err = 0; @@ -441,12 +540,7 @@ bool git_path_contains_file(git_buf *base, const char *file) int git_path_find_dir(git_buf *dir, const char *path, const char *base) { - int error; - - if (base != NULL && git_path_root(path) < 0) - error = git_buf_joinpath(dir, base, path); - else - error = git_buf_sets(dir, path); + int error = git_path_join_unrooted(dir, path, base, NULL); if (!error) { char buf[GIT_PATH_MAX]; @@ -464,6 +558,71 @@ int git_path_find_dir(git_buf *dir, const char *path, const char *base) return error; } +int git_path_resolve_relative(git_buf *path, size_t ceiling) +{ + char *base, *to, *from, *next; + size_t len; + + if (!path || git_buf_oom(path)) + return -1; + + if (ceiling > path->size) + ceiling = path->size; + + /* recognize drive prefixes, etc. that should not be backed over */ + if (ceiling == 0) + ceiling = git_path_root(path->ptr) + 1; + + /* recognize URL prefixes that should not be backed over */ + if (ceiling == 0) { + for (next = path->ptr; *next && git__isalpha(*next); ++next); + if (next[0] == ':' && next[1] == '/' && next[2] == '/') + ceiling = (next + 3) - path->ptr; + } + + base = to = from = path->ptr + ceiling; + + while (*from) { + for (next = from; *next && *next != '/'; ++next); + + len = next - from; + + if (len == 1 && from[0] == '.') + /* do nothing with singleton dot */; + + else if (len == 2 && from[0] == '.' && from[1] == '.') { + while (to > base && to[-1] == '/') to--; + while (to > base && to[-1] != '/') to--; + } + + else { + if (*next == '/') + len++; + + if (to != from) + memmove(to, from, len); + + to += len; + } + + from += len; + + while (*from == '/') from++; + } + + *to = '\0'; + + path->size = to - path->ptr; + + return 0; +} + +int git_path_apply_relative(git_buf *target, const char *relpath) +{ + git_buf_joinpath(target, git_buf_cstr(target), relpath); + return git_path_resolve_relative(target, 0); +} + int git_path_cmp( const char *name1, size_t len1, int isdir1, const char *name2, size_t len2, int isdir2) @@ -488,14 +647,6 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } -/* Taken from git.git */ -GIT_INLINE(int) is_dot_or_dotdot(const char *name) -{ - return (name[0] == '.' && - (name[1] == '\0' || - (name[1] == '.' && name[2] == '\0'))); -} - int git_path_direach( git_buf *path, int (*fn)(void *, git_buf *), @@ -515,7 +666,7 @@ int git_path_direach( return -1; } -#ifdef __sun +#if defined(__sun) || defined(__GNU__) de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); #else de_buf = git__malloc(sizeof(struct dirent)); @@ -524,7 +675,7 @@ int git_path_direach( while (p_readdir_r(dir, de_buf, &de) == 0 && de != NULL) { int result; - if (is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de->d_name)) continue; if (git_buf_puts(path, de->d_name) < 0) { @@ -569,7 +720,7 @@ int git_path_dirload( return -1; } -#ifdef __sun +#if defined(__sun) || defined(__GNU__) de_buf = git__malloc(sizeof(struct dirent) + FILENAME_MAX + 1); #else de_buf = git__malloc(sizeof(struct dirent)); @@ -583,7 +734,7 @@ int git_path_dirload( char *entry_path; size_t entry_len; - if (is_dot_or_dotdot(de->d_name)) + if (git_path_is_dot_or_dotdot(de->d_name)) continue; entry_len = strlen(de->d_name); diff --git a/src/path.h b/src/path.h index fd76805e5ef..b6292277ffd 100644 --- a/src/path.h +++ b/src/path.h @@ -58,6 +58,11 @@ extern int git_path_dirname_r(git_buf *buffer, const char *path); extern char *git_path_basename(const char *path); extern int git_path_basename_r(git_buf *buffer, const char *path); +/* Return the offset of the start of the basename. Unlike the other + * basename functions, this returns 0 if the path is empty. + */ +extern size_t git_path_basename_offset(git_buf *buffer); + extern const char *git_path_topdir(const char *path); /** @@ -80,7 +85,24 @@ extern int git_path_to_dir(git_buf *path); */ extern void git_path_string_to_dir(char* path, size_t size); +/** + * Taken from git.git; returns nonzero if the given path is "." or "..". + */ +GIT_INLINE(int) git_path_is_dot_or_dotdot(const char *name) +{ + return (name[0] == '.' && + (name[1] == '\0' || + (name[1] == '.' && name[2] == '\0'))); +} + #ifdef GIT_WIN32 +GIT_INLINE(int) git_path_is_dot_or_dotdotW(const wchar_t *name) +{ + return (name[0] == L'.' && + (name[1] == L'\0' || + (name[1] == L'.' && name[2] == L'\0'))); +} + /** * Convert backslashes in path to forward slashes. */ @@ -129,6 +151,11 @@ extern bool git_path_isdir(const char *path); */ extern bool git_path_isfile(const char *path); +/** + * Check if the given path is a directory, and is empty. + */ +extern bool git_path_is_empty_dir(const char *path); + /** * Stat a file and/or link and set error if needed. */ @@ -163,6 +190,15 @@ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); */ extern bool git_path_contains_file(git_buf *dir, const char *file); +/** + * Prepend base to unrooted path or just copy path over. + * + * This will optionally return the index into the path where the "root" + * is, either the end of the base directory prefix or the path root. + */ +extern int git_path_join_unrooted( + git_buf *path_out, const char *path, const char *base, ssize_t *root_at); + /** * Clean up path, prepending base if it is not already rooted. */ @@ -185,6 +221,29 @@ extern int git_path_prettify_dir(git_buf *path_out, const char *path, const char */ extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); +/** + * Resolve relative references within a path. + * + * This eliminates "./" and "../" relative references inside a path, + * as well as condensing multiple slashes into single ones. It will + * not touch the path before the "ceiling" length. + * + * Additionally, this will recognize an "c:/" drive prefix or a "xyz://" URL + * prefix and not touch that part of the path. + */ +extern int git_path_resolve_relative(git_buf *path, size_t ceiling); + +/** + * Apply a relative path to base path. + * + * Note that the base path could be a filename or a URL and this + * should still work. The relative path is walked segment by segment + * with three rules: series of slashes will be condensed to a single + * slash, "." will be eaten with no change, and ".." will remove a + * segment from the base path. + */ +extern int git_path_apply_relative(git_buf *target, const char *relpath); + /** * Walk each directory entry, except '.' and '..', calling fn(state). * @@ -194,6 +253,7 @@ extern int git_path_find_dir(git_buf *dir, const char *path, const char *base); * the input state and the second arg is pathbuf. The function * may modify the pathbuf, but only by appending new text. * @param state to pass to fn as the first arg. + * @return 0 on success, GIT_EUSER on non-zero callback, or error code */ extern int git_path_direach( git_buf *pathbuf, diff --git a/src/pkt.c b/src/pkt.c index 88510f4b1f1..ad0149d3395 100644 --- a/src/pkt.c +++ b/src/pkt.c @@ -17,6 +17,7 @@ #include "netops.h" #include "posix.h" #include "buffer.h" +#include "protocol.h" #include @@ -42,15 +43,29 @@ static int flush_pkt(git_pkt **out) /* the rest of the line will be useful for multi_ack */ static int ack_pkt(git_pkt **out, const char *line, size_t len) { - git_pkt *pkt; + git_pkt_ack *pkt; GIT_UNUSED(line); GIT_UNUSED(len); - pkt = git__malloc(sizeof(git_pkt)); + pkt = git__calloc(1, sizeof(git_pkt_ack)); GITERR_CHECK_ALLOC(pkt); pkt->type = GIT_PKT_ACK; - *out = pkt; + line += 3; + len -= 3; + + if (len >= GIT_OID_HEXSZ) { + git_oid_fromstr(&pkt->oid, line + 1); + line += GIT_OID_HEXSZ + 1; + len -= GIT_OID_HEXSZ + 1; + } + + if (len >= 7) { + if (!git__prefixcmp(line + 1, "continue")) + pkt->status = GIT_ACK_CONTINUE; + } + + *out = (git_pkt *) pkt; return 0; } @@ -116,6 +131,42 @@ static int err_pkt(git_pkt **out, const char *line, size_t len) return 0; } +static int data_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_data *pkt; + + line++; + len--; + pkt = git__malloc(sizeof(git_pkt_data) + len); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_DATA; + pkt->len = (int) len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + +static int progress_pkt(git_pkt **out, const char *line, size_t len) +{ + git_pkt_progress *pkt; + + line++; + len--; + pkt = git__malloc(sizeof(git_pkt_progress) + len); + GITERR_CHECK_ALLOC(pkt); + + pkt->type = GIT_PKT_PROGRESS; + pkt->len = (int) len; + memcpy(pkt->data, line, len); + + *out = (git_pkt *) pkt; + + return 0; +} + /* * Parse an other-ref line. */ @@ -249,8 +300,11 @@ int git_pkt_parse_line( len -= PKT_LEN_SIZE; /* the encoded length includes its own size */ - /* Assming the minimal size is actually 4 */ - if (!git__prefixcmp(line, "ACK")) + if (*line == GIT_SIDE_BAND_DATA) + ret = data_pkt(head, line, len); + else if (*line == GIT_SIDE_BAND_PROGRESS) + ret = progress_pkt(head, line, len); + else if (!git__prefixcmp(line, "ACK")) ret = ack_pkt(head, line, len); else if (!git__prefixcmp(line, "NAK")) ret = nak_pkt(head); @@ -283,20 +337,35 @@ int git_pkt_buffer_flush(git_buf *buf) static int buffer_want_with_caps(git_remote_head *head, git_transport_caps *caps, git_buf *buf) { - char capstr[20]; + git_buf str = GIT_BUF_INIT; char oid[GIT_OID_HEXSZ +1] = {0}; unsigned int len; + /* Prefer side-band-64k if the server supports both */ + if (caps->side_band) { + if (caps->side_band_64k) + git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND_64K); + else + git_buf_printf(&str, "%s ", GIT_CAP_SIDE_BAND); + } if (caps->ofs_delta) - strncpy(capstr, GIT_CAP_OFS_DELTA, sizeof(capstr)); + git_buf_puts(&str, GIT_CAP_OFS_DELTA " "); + + if (caps->multi_ack) + git_buf_puts(&str, GIT_CAP_MULTI_ACK " "); + + if (git_buf_oom(&str)) + return -1; len = (unsigned int) (strlen("XXXXwant ") + GIT_OID_HEXSZ + 1 /* NUL */ + - strlen(capstr) + 1 /* LF */); + git_buf_len(&str) + 1 /* LF */); git_buf_grow(buf, git_buf_len(buf) + len); - git_oid_fmt(oid, &head->oid); - return git_buf_printf(buf, "%04xwant %s%c%s\n", len, oid, 0, capstr); + git_buf_printf(buf, "%04xwant %s %s\n", len, oid, git_buf_cstr(&str)); + git_buf_free(&str); + + return git_buf_oom(buf); } /* diff --git a/src/pkt.h b/src/pkt.h index 75442c83376..0fdb5c7cde5 100644 --- a/src/pkt.h +++ b/src/pkt.h @@ -24,6 +24,8 @@ enum git_pkt_type { GIT_PKT_PACK, GIT_PKT_COMMENT, GIT_PKT_ERR, + GIT_PKT_DATA, + GIT_PKT_PROGRESS, }; /* Used for multi-ack */ @@ -65,6 +67,14 @@ typedef struct { 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]; diff --git a/src/pool.c b/src/pool.c index 63bf09ceec1..64b5c6b0041 100644 --- a/src/pool.c +++ b/src/pool.c @@ -206,6 +206,11 @@ char *git_pool_strdup(git_pool *pool, const char *str) return git_pool_strndup(pool, str, strlen(str)); } +char *git_pool_strdup_safe(git_pool *pool, const char *str) +{ + return str ? git_pool_strdup(pool, str) : NULL; +} + char *git_pool_strcat(git_pool *pool, const char *a, const char *b) { void *ptr; diff --git a/src/pool.h b/src/pool.h index 54a2861edce..dee6ecdae11 100644 --- a/src/pool.h +++ b/src/pool.h @@ -75,6 +75,17 @@ extern void git_pool_swap(git_pool *a, git_pool *b); */ extern void *git_pool_malloc(git_pool *pool, uint32_t items); +/** + * Allocate space and zero it out. + */ +GIT_INLINE(void *) git_pool_mallocz(git_pool *pool, uint32_t items) +{ + void *ptr = git_pool_malloc(pool, items); + if (ptr) + memset(ptr, 0, (size_t)items * (size_t)pool->item_size); + return ptr; +} + /** * Allocate space and duplicate string data into it. * @@ -89,6 +100,13 @@ extern char *git_pool_strndup(git_pool *pool, const char *str, size_t n); */ extern char *git_pool_strdup(git_pool *pool, const char *str); +/** + * Allocate space and duplicate a string into it, NULL is no error. + * + * This is allowed only for pools with item_size == sizeof(char) + */ +extern char *git_pool_strdup_safe(git_pool *pool, const char *str); + /** * Allocate space for the concatenation of two strings. * diff --git a/src/posix.h b/src/posix.h index d35fe08a573..71bb8228308 100644 --- a/src/posix.h +++ b/src/posix.h @@ -11,8 +11,15 @@ #include #include +#ifndef S_IFGITLINK #define S_IFGITLINK 0160000 #define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK) +#endif + +/* if S_ISGID is not defined, then don't try to set it */ +#ifndef S_ISGID +#define S_ISGID 0 +#endif #if !defined(O_BINARY) #define O_BINARY 0 diff --git a/src/protocol.c b/src/protocol.c index 6b38617963a..4526c857de9 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -9,38 +9,36 @@ #include "pkt.h" #include "buffer.h" -int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) +int git_protocol_store_refs(git_transport *t, int flushes) { - git_buf *buf = &p->buf; - git_vector *refs = p->refs; - int error; - const char *line_end, *ptr; - - if (len == 0) { /* EOF */ - if (git_buf_len(buf) != 0) { - giterr_set(GITERR_NET, "Unexpected EOF"); - return p->error = -1; - } else { - return 0; - } - } + 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; - git_buf_put(buf, data, len); - ptr = buf->ptr; - while (1) { - git_pkt *pkt; + if (error < 0 && error != GIT_EBUFS) + return -1; - if (git_buf_len(buf) == 0) - return 0; + if (error == GIT_EBUFS) { + if ((recvd = gitno_recv(buf)) < 0) + return -1; - error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); - if (error == GIT_EBUFS) - return 0; /* Ask for more */ - if (error < 0) - return p->error = -1; + if (recvd == 0 && !flush) { + giterr_set(GITERR_NET, "Early EOF"); + return -1; + } - git_buf_consume(buf, line_end); + 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); @@ -48,10 +46,56 @@ int git_protocol_store_refs(git_protocol *p, const char *data, size_t len) } if (git_vector_insert(refs, pkt) < 0) - return p->error = -1; + return -1; if (pkt->type == GIT_PKT_FLUSH) - p->flush = 1; + flush++; + } 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; + } + + /* 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 index a6c3e0735f7..a990938e56b 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -9,15 +9,13 @@ #include "transport.h" #include "buffer.h" +#include "pkt.h" -typedef struct { - git_transport *transport; - git_vector *refs; - git_buf buf; - int error; - unsigned int flush :1; -} git_protocol; +int git_protocol_store_refs(git_transport *t, int flushes); +int git_protocol_detect_caps(git_pkt_ref *pkt, git_transport_caps *caps); -int git_protocol_store_refs(git_protocol *p, const char *data, size_t len); +#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 3ea073e6577..80e40b960fd 100644 --- a/src/reflog.c +++ b/src/reflog.c @@ -28,66 +28,68 @@ static int reflog_init(git_reflog **reflog, git_reference *ref) return -1; } + log->owner = git_reference_owner(ref); *reflog = log; return 0; } -static int reflog_write(const char *log_path, const char *oid_old, - const char *oid_new, const git_signature *committer, - const char *msg) +static int serialize_reflog_entry( + git_buf *buf, + const git_oid *oid_old, + const git_oid *oid_new, + const git_signature *committer, + const char *msg) { - int error; - git_buf log = GIT_BUF_INIT; - git_filebuf fbuf = GIT_FILEBUF_INIT; - bool trailing_newline = false; + char raw_old[GIT_OID_HEXSZ+1]; + char raw_new[GIT_OID_HEXSZ+1]; - assert(log_path && oid_old && oid_new && committer); + git_oid_tostr(raw_old, GIT_OID_HEXSZ+1, oid_old); + git_oid_tostr(raw_new, GIT_OID_HEXSZ+1, oid_new); - if (msg) { - const char *newline = strchr(msg, '\n'); - if (newline) { - if (*(newline + 1) == '\0') - trailing_newline = true; - else { - giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); - return -1; - } - } - } + git_buf_clear(buf); - git_buf_puts(&log, oid_old); - git_buf_putc(&log, ' '); + git_buf_puts(buf, raw_old); + git_buf_putc(buf, ' '); + git_buf_puts(buf, raw_new); - git_buf_puts(&log, oid_new); + git_signature__writebuf(buf, " ", committer); - git_signature__writebuf(&log, " ", committer); - git_buf_truncate(&log, log.size - 1); /* drop LF */ + /* drop trailing LF */ + git_buf_rtrim(buf); if (msg) { - git_buf_putc(&log, '\t'); - git_buf_puts(&log, msg); + git_buf_putc(buf, '\t'); + git_buf_puts(buf, msg); } - if (!trailing_newline) - git_buf_putc(&log, '\n'); + git_buf_putc(buf, '\n'); - if (git_buf_oom(&log)) { - git_buf_free(&log); - return -1; - } + return git_buf_oom(buf); +} - error = git_filebuf_open(&fbuf, log_path, GIT_FILEBUF_APPEND); - if (!error) { - if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) - git_filebuf_cleanup(&fbuf); - else - error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); - } +static int reflog_entry_new(git_reflog_entry **entry) +{ + git_reflog_entry *e; - git_buf_free(&log); + assert(entry); - return error; + e = git__malloc(sizeof(git_reflog_entry)); + GITERR_CHECK_ALLOC(e); + + memset(e, 0, sizeof(git_reflog_entry)); + + *entry = e; + + return 0; +} + +static void reflog_entry_free(git_reflog_entry *entry) +{ + git_signature_free(entry->committer); + + git__free(entry->msg); + git__free(entry); } static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) @@ -105,8 +107,8 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) } while (0) while (buf_size > GIT_REFLOG_SIZE_MIN) { - entry = git__malloc(sizeof(git_reflog_entry)); - GITERR_CHECK_ALLOC(entry); + if (reflog_entry_new(&entry) < 0) + return -1; entry->committer = git__malloc(sizeof(git_signature)); GITERR_CHECK_ALLOC(entry->committer); @@ -153,10 +155,9 @@ static int reflog_parse(git_reflog *log, const char *buf, size_t buf_size) #undef seek_forward fail: - if (entry) { - git__free(entry->committer); - git__free(entry); - } + if (entry) + reflog_entry_free(entry); + return -1; } @@ -168,10 +169,7 @@ void git_reflog_free(git_reflog *reflog) for (i=0; i < reflog->entries.length; i++) { entry = git_vector_get(&reflog->entries, i); - git_signature_free(entry->committer); - - git__free(entry->msg); - git__free(entry); + reflog_entry_free(entry); } git_vector_free(&reflog->entries); @@ -179,108 +177,211 @@ void git_reflog_free(git_reflog *reflog) git__free(reflog); } +static int retrieve_reflog_path(git_buf *path, git_reference *ref) +{ + return git_buf_join_n(path, '/', 3, + git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR, ref->name); +} + +static int create_new_reflog_file(const char *filepath) +{ + int fd; + + if ((fd = p_open(filepath, + O_WRONLY | O_CREAT | O_TRUNC, + GIT_REFLOG_FILE_MODE)) < 0) + return -1; + + return p_close(fd); +} + int git_reflog_read(git_reflog **reflog, git_reference *ref) { - int error; + int error = -1; git_buf log_path = GIT_BUF_INIT; git_buf log_file = GIT_BUF_INIT; git_reflog *log = NULL; *reflog = NULL; + assert(reflog && ref); + if (reflog_init(&log, ref) < 0) return -1; - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + if (retrieve_reflog_path(&log_path, ref) < 0) + goto cleanup; - if (!error) - error = git_futils_readbuffer(&log_file, log_path.ptr); + error = git_futils_readbuffer(&log_file, git_buf_cstr(&log_path)); + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; - if (!error) - error = reflog_parse(log, log_file.ptr, log_file.size); + if ((error == GIT_ENOTFOUND) && + ((error = create_new_reflog_file(git_buf_cstr(&log_path))) < 0)) + goto cleanup; + + if ((error = reflog_parse(log, + git_buf_cstr(&log_file), git_buf_len(&log_file))) < 0) + goto cleanup; + + *reflog = log; + goto success; - if (!error) - *reflog = log; - else - git_reflog_free(log); +cleanup: + git_reflog_free(log); +success: git_buf_free(&log_file); git_buf_free(&log_path); return error; } -int git_reflog_write(git_reference *ref, const git_oid *oid_old, - const git_signature *committer, const char *msg) +int git_reflog_write(git_reflog *reflog) { - int error; - char old[GIT_OID_HEXSZ+1]; - char new[GIT_OID_HEXSZ+1]; + int error = -1; + unsigned int i; + git_reflog_entry *entry; git_buf log_path = GIT_BUF_INIT; - git_reference *r; - const git_oid *oid; + git_buf log = GIT_BUF_INIT; + git_filebuf fbuf = GIT_FILEBUF_INIT; + + assert(reflog); - if ((error = git_reference_resolve(&r, ref)) < 0) - return error; - oid = git_reference_oid(r); - if (oid == NULL) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. Cannot resolve reference `%s`", r->name); - git_reference_free(r); + if (git_buf_join_n(&log_path, '/', 3, + git_repository_path(reflog->owner), GIT_REFLOG_DIR, reflog->ref_name) < 0) return -1; + + if (!git_path_isfile(git_buf_cstr(&log_path))) { + giterr_set(GITERR_INVALID, + "Log file for reference '%s' doesn't exist.", reflog->ref_name); + goto cleanup; } - git_oid_tostr(new, GIT_OID_HEXSZ+1, oid); + if ((error = git_filebuf_open(&fbuf, git_buf_cstr(&log_path), 0)) < 0) + goto cleanup; + + git_vector_foreach(&reflog->entries, i, entry) { + if (serialize_reflog_entry(&log, &(entry->oid_old), &(entry->oid_cur), entry->committer, entry->msg) < 0) + goto cleanup; + + if ((error = git_filebuf_write(&fbuf, log.ptr, log.size)) < 0) + goto cleanup; + } + + error = git_filebuf_commit(&fbuf, GIT_REFLOG_FILE_MODE); + goto success; + +cleanup: + git_filebuf_cleanup(&fbuf); + +success: + git_buf_free(&log); + git_buf_free(&log_path); + return error; +} - git_reference_free(r); +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 char *newline; - error = git_buf_join_n(&log_path, '/', 3, - ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); - if (error < 0) + assert(reflog && new_oid && committer); + + if (reflog_entry_new(&entry) < 0) + return -1; + + if ((entry->committer = git_signature_dup(committer)) == NULL) goto cleanup; - if (git_path_exists(log_path.ptr) == false) { - error = git_futils_mkpath2file(log_path.ptr, GIT_REFLOG_DIR_MODE); - } else if (git_path_isfile(log_path.ptr) == false) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. `%s` is directory", log_path.ptr); - error = -1; - } else if (oid_old == NULL) { - giterr_set(GITERR_REFERENCE, - "Failed to write reflog. Old OID cannot be NULL for existing reference"); - error = -1; + if (msg != NULL) { + if ((entry->msg = git__strdup(msg)) == NULL) + goto cleanup; + + newline = strchr(msg, '\n'); + + if (newline) { + if (newline[1] != '\0') { + giterr_set(GITERR_INVALID, "Reflog message cannot contain newline"); + goto cleanup; + } + + entry->msg[newline - msg] = '\0'; + } } - if (error < 0) - goto cleanup; - if (oid_old) - git_oid_tostr(old, sizeof(old), oid_old); - else - p_snprintf(old, sizeof(old), "%0*d", GIT_OID_HEXSZ, 0); + count = git_reflog_entrycount(reflog); + + if (count == 0) + git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO); + else { + const git_reflog_entry *previous; + + previous = git_reflog_entry_byindex(reflog, count -1); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + } + + git_oid_cpy(&entry->oid_cur, new_oid); + + if (git_vector_insert(&reflog->entries, entry) < 0) + goto cleanup; - error = reflog_write(log_path.ptr, old, new, committer, msg); + return 0; cleanup: - git_buf_free(&log_path); - return error; + reflog_entry_free(entry); + return -1; } int git_reflog_rename(git_reference *ref, const char *new_name) { - int error; + int error = -1, fd; git_buf old_path = GIT_BUF_INIT; git_buf new_path = GIT_BUF_INIT; + git_buf temp_path = GIT_BUF_INIT; + + assert(ref && new_name); + + if (git_buf_joinpath(&temp_path, git_reference_owner(ref)->path_repository, GIT_REFLOG_DIR) < 0) + return -1; + + if (git_buf_joinpath(&old_path, git_buf_cstr(&temp_path), ref->name) < 0) + goto cleanup; - if (!git_buf_join_n(&old_path, '/', 3, ref->owner->path_repository, - GIT_REFLOG_DIR, ref->name) && - !git_buf_join_n(&new_path, '/', 3, ref->owner->path_repository, - GIT_REFLOG_DIR, new_name)) - error = p_rename(git_buf_cstr(&old_path), git_buf_cstr(&new_path)); - else - error = -1; + if (git_buf_joinpath(&new_path, git_buf_cstr(&temp_path), new_name) < 0) + goto cleanup; + /* + * Move the reflog to a temporary place. This two-phase renaming is required + * in order to cope with funny renaming use cases when one tries to move a reference + * to a partially colliding namespace: + * - a/b -> a/b/c + * - a/b/c/d -> a/b/c + */ + if (git_buf_joinpath(&temp_path, git_buf_cstr(&temp_path), "temp_reflog") < 0) + goto cleanup; + + if ((fd = git_futils_mktmp(&temp_path, git_buf_cstr(&temp_path))) < 0) + goto cleanup; + p_close(fd); + + if (p_rename(git_buf_cstr(&old_path), git_buf_cstr(&temp_path)) < 0) + goto cleanup; + + if (git_path_isdir(git_buf_cstr(&new_path)) && + (git_futils_rmdir_r(git_buf_cstr(&new_path), GIT_DIRREMOVAL_ONLY_EMPTY_DIRS) < 0)) + goto cleanup; + + if (git_futils_mkpath2file(git_buf_cstr(&new_path), GIT_REFLOG_DIR_MODE) < 0) + goto cleanup; + + error = p_rename(git_buf_cstr(&temp_path), git_buf_cstr(&new_path)); + +cleanup: + git_buf_free(&temp_path); git_buf_free(&old_path); git_buf_free(&new_path); @@ -292,8 +393,7 @@ int git_reflog_delete(git_reference *ref) int error; git_buf path = GIT_BUF_INIT; - error = git_buf_join_n( - &path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name); + error = retrieve_reflog_path(&path, ref); if (!error && git_path_exists(path.ptr)) error = p_unlink(path.ptr); @@ -306,10 +406,10 @@ int git_reflog_delete(git_reference *ref) unsigned int git_reflog_entrycount(git_reflog *reflog) { assert(reflog); - return reflog->entries.length; + return (unsigned int)reflog->entries.length; } -const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, unsigned int idx) +const git_reflog_entry * git_reflog_entry_byindex(git_reflog *reflog, size_t idx) { assert(reflog); return git_vector_get(&reflog->entries, idx); @@ -338,3 +438,52 @@ char * git_reflog_entry_msg(const git_reflog_entry *entry) assert(entry); return entry->msg; } + +int git_reflog_drop( + git_reflog *reflog, + unsigned int idx, + int rewrite_previous_entry) +{ + unsigned int entrycount; + git_reflog_entry *entry, *previous; + + assert(reflog); + + entrycount = git_reflog_entrycount(reflog); + + if (idx >= entrycount) + return GIT_ENOTFOUND; + + entry = git_vector_get(&reflog->entries, idx); + reflog_entry_free(entry); + + if (git_vector_remove(&reflog->entries, idx) < 0) + return -1; + + if (!rewrite_previous_entry) + return 0; + + /* No need to rewrite anything when removing the first entry */ + if (idx == 0) + return 0; + + /* There are no more entries in the log */ + 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 (idx == entrycount - 1) { + /* ...clear the oid_old member of the "new" last entry */ + if (git_oid_fromstr(&entry->oid_old, GIT_OID_HEX_ZERO) < 0) + return -1; + + return 0; + } + + previous = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx); + git_oid_cpy(&entry->oid_old, &previous->oid_cur); + + return 0; +} diff --git a/src/reflog.h b/src/reflog.h index 33cf0776cc3..3bbdf6e1042 100644 --- a/src/reflog.h +++ b/src/reflog.h @@ -17,6 +17,8 @@ #define GIT_REFLOG_SIZE_MIN (2*GIT_OID_HEXSZ+2+17) +#define GIT_OID_HEX_ZERO "0000000000000000000000000000000000000000" + struct git_reflog_entry { git_oid oid_old; git_oid oid_cur; @@ -28,6 +30,7 @@ struct git_reflog_entry { struct git_reflog { char *ref_name; + git_repository *owner; git_vector entries; }; diff --git a/src/refs.c b/src/refs.c index ee076b3b875..74c40e85089 100644 --- a/src/refs.c +++ b/src/refs.c @@ -14,6 +14,7 @@ #include #include +#include GIT__USE_STRMAP; @@ -67,11 +68,6 @@ static int reference_path_available(git_repository *repo, static int reference_delete(git_reference *ref); static int reference_lookup(git_reference *ref); -/* name normalization */ -static int normalize_name(char *buffer_out, size_t out_size, - const char *name, int is_oid_ref); - - void git_reference_free(git_reference *reference) { if (reference == NULL) @@ -128,6 +124,7 @@ static int reference_read( result = git_futils_readbuffer_updated(file_content, path.ptr, mtime, updated); git_buf_free(&path); + return result; } @@ -135,12 +132,13 @@ static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) { const unsigned int header_len = (unsigned int)strlen(GIT_SYMREF); const char *refname_start; - char *eol; refname_start = (const char *)file_content->ptr; - if (git_buf_len(file_content) < header_len + 1) - goto corrupt; + if (git_buf_len(file_content) < header_len + 1) { + giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); + return -1; + } /* * Assume we have already checked for the header @@ -151,45 +149,16 @@ static int loose_parse_symbolic(git_reference *ref, git_buf *file_content) ref->target.symbolic = git__strdup(refname_start); GITERR_CHECK_ALLOC(ref->target.symbolic); - /* remove newline at the end of file */ - eol = strchr(ref->target.symbolic, '\n'); - if (eol == NULL) - goto corrupt; - - *eol = '\0'; - if (eol[-1] == '\r') - eol[-1] = '\0'; - return 0; - -corrupt: - giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); - return -1; } static int loose_parse_oid(git_oid *oid, git_buf *file_content) { - char *buffer; - - buffer = (char *)file_content->ptr; - - /* File format: 40 chars (OID) + newline */ - if (git_buf_len(file_content) < GIT_OID_HEXSZ + 1) - goto corrupt; - - if (git_oid_fromstr(oid, buffer) < 0) - goto corrupt; - - buffer = buffer + GIT_OID_HEXSZ; - if (*buffer == '\r') - buffer++; - - if (*buffer != '\n') - goto corrupt; - - return 0; + /* File format: 40 chars (OID) */ + if (git_buf_len(file_content) == GIT_OID_HEXSZ && + git_oid_fromstr(oid, git_buf_cstr(file_content)) == 0) + return 0; -corrupt: giterr_set(GITERR_REFERENCE, "Corrupted loose reference file"); return -1; } @@ -226,6 +195,8 @@ static int loose_lookup(git_reference *ref) if (!updated) return 0; + git_buf_rtrim(&ref_file); + if (ref->flags & GIT_REF_SYMBOLIC) { git__free(ref->target.symbolic); ref->target.symbolic = NULL; @@ -259,6 +230,8 @@ static int loose_lookup_to_packfile( if (reference_read(&ref_file, NULL, repo->path_repository, name, NULL) < 0) return -1; + git_buf_rtrim(&ref_file); + name_len = strlen(name); ref = git__malloc(sizeof(struct packref) + name_len + 1); GITERR_CHECK_ALLOC(ref); @@ -500,6 +473,7 @@ struct dirent_list_data { int (*callback)(const char *, void *); void *callback_payload; + int callback_error; }; static int _dirent_loose_listall(void *_data, git_buf *full_path) @@ -520,7 +494,14 @@ static int _dirent_loose_listall(void *_data, git_buf *full_path) return 0; /* we are filtering out this reference */ } - return data->callback(file_path, data->callback_payload); + /* Locked references aren't returned */ + if (!git__suffixcmp(file_path, GIT_FILELOCK_EXTENSION)) + return 0; + + if (data->callback(file_path, data->callback_payload)) + data->callback_error = GIT_EUSER; + + return data->callback_error; } static int _dirent_loose_load(void *data, git_buf *full_path) @@ -843,15 +824,17 @@ static int reference_path_available( const char *ref, const char* old_ref) { + int error; struct reference_available_t data; data.new_ref = ref; data.old_ref = old_ref; data.available = 1; - if (git_reference_foreach(repo, GIT_REF_LISTALL, - _reference_available_cb, (void *)&data) < 0) - return -1; + error = git_reference_foreach( + repo, GIT_REF_LISTALL, _reference_available_cb, (void *)&data); + if (error < 0) + return error; if (!data.available) { giterr_set(GITERR_REFERENCE, @@ -1115,9 +1098,12 @@ int git_reference_lookup_resolved( scan->name = git__calloc(GIT_REFNAME_MAX + 1, sizeof(char)); GITERR_CHECK_ALLOC(scan->name); - if ((result = normalize_name(scan->name, GIT_REFNAME_MAX, name, 0)) < 0) { - git_reference_free(scan); - return result; + if ((result = git_reference__normalize_name( + scan->name, + GIT_REFNAME_MAX, + name)) < 0) { + git_reference_free(scan); + return result; } scan->target.symbolic = git__strdup(scan->name); @@ -1214,8 +1200,11 @@ int git_reference_create_symbolic( char normalized[GIT_REFNAME_MAX]; git_reference *ref = NULL; - if (normalize_name(normalized, sizeof(normalized), name, 0) < 0) - return -1; + if (git_reference__normalize_name( + normalized, + sizeof(normalized), + name) < 0) + return -1; if (reference_can_write(repo, normalized, NULL, force) < 0) return -1; @@ -1250,8 +1239,11 @@ int git_reference_create_oid( git_reference *ref = NULL; char normalized[GIT_REFNAME_MAX]; - if (normalize_name(normalized, sizeof(normalized), name, 1) < 0) - return -1; + if (git_reference__normalize_name_oid( + normalized, + sizeof(normalized), + name) < 0) + return -1; if (reference_can_write(repo, normalized, NULL, force) < 0) return -1; @@ -1330,8 +1322,11 @@ int git_reference_set_target(git_reference *ref, const char *target) return -1; } - if (normalize_name(normalized, sizeof(normalized), target, 0)) - return -1; + if (git_reference__normalize_name( + normalized, + sizeof(normalized), + target)) + return -1; git__free(ref->target.symbolic); ref->target.symbolic = git__strdup(normalized); @@ -1343,15 +1338,23 @@ int git_reference_set_target(git_reference *ref, const char *target) int git_reference_rename(git_reference *ref, const char *new_name, int force) { int result; + unsigned int normalization_flags; git_buf aux_path = GIT_BUF_INIT; char normalized[GIT_REFNAME_MAX]; const char *head_target = NULL; git_reference *head = NULL; - if (normalize_name(normalized, sizeof(normalized), - new_name, ref->flags & GIT_REF_OID) < 0) - return -1; + normalization_flags = ref->flags & GIT_REF_SYMBOLIC ? + GIT_REF_FORMAT_ALLOW_ONELEVEL + : GIT_REF_FORMAT_NORMAL; + + if (git_reference_normalize_name( + normalized, + sizeof(normalized), + new_name, + normalization_flags) < 0) + return -1; if (reference_can_write(ref->owner, normalized, ref->name, force) < 0) return -1; @@ -1396,6 +1399,9 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) head_target = git_reference_target(head); if (head_target && !strcmp(head_target, ref->name)) { + git_reference_free(head); + head = NULL; + if (git_reference_create_symbolic(&head, ref->owner, "HEAD", new_name, 1) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); @@ -1404,18 +1410,11 @@ int git_reference_rename(git_reference *ref, const char *new_name, int force) } /* - * Rename the reflog file. + * Rename the reflog file, if it exists. */ - if (git_buf_join_n(&aux_path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + if ((git_reference_has_log(ref)) && (git_reflog_rename(ref, new_name) < 0)) goto cleanup; - if (git_path_exists(aux_path.ptr) == true) { - if (git_reflog_rename(ref, new_name) < 0) - goto cleanup; - } else { - giterr_clear(); - } - /* * Change the name of the reference given by the user. */ @@ -1490,8 +1489,8 @@ int git_reference_foreach( return -1; git_strmap_foreach(repo->references.packfile, ref_name, ref, { - if (callback(ref_name, payload) < 0) - return 0; + if (callback(ref_name, payload)) + return GIT_EUSER; }); } @@ -1503,14 +1502,16 @@ int git_reference_foreach( data.repo = repo; data.callback = callback; data.callback_payload = payload; + data.callback_error = 0; if (git_buf_joinpath(&refs_path, repo->path_repository, GIT_REFS_DIR) < 0) return -1; result = git_path_direach(&refs_path, _dirent_loose_listall, &data); + git_buf_free(&refs_path); - return result; + return data.callback_error ? GIT_EUSER : result; } static int cb__reflist_add(const char *ref, void *data) @@ -1583,11 +1584,11 @@ static int is_valid_ref_char(char ch) } } -static int normalize_name( +int git_reference_normalize_name( char *buffer_out, - size_t out_size, + size_t buffer_size, const char *name, - int is_oid_ref) + unsigned int flags) { const char *name_end, *buffer_out_start; const char *current; @@ -1595,12 +1596,17 @@ static int normalize_name( assert(name && buffer_out); + if (flags & GIT_REF_FORMAT_REFSPEC_PATTERN) { + giterr_set(GITERR_INVALID, "Unimplemented"); + return -1; + } + buffer_out_start = buffer_out; current = name; name_end = name + strlen(name); /* Terminating null byte */ - out_size--; + buffer_size--; /* A refname can not be empty */ if (name_end == name) @@ -1610,7 +1616,7 @@ static int normalize_name( if (*(name_end - 1) == '.' || *(name_end - 1) == '/') goto invalid_name; - while (current < name_end && out_size) { + while (current < name_end && buffer_size > 0) { if (!is_valid_ref_char(*current)) goto invalid_name; @@ -1632,20 +1638,30 @@ static int normalize_name( } } - if (*current == '/') - contains_a_slash = 1; + if (*current == '/') { + if (buffer_out > buffer_out_start) + contains_a_slash = 1; + else { + current++; + continue; + } + } *buffer_out++ = *current++; - out_size--; + buffer_size--; } - if (!out_size) - goto invalid_name; + if (current < name_end) { + giterr_set( + GITERR_REFERENCE, + "The provided buffer is too short to hold the normalization of '%s'", name); + return GIT_EBUFS; + } /* Object id refname have to contain at least one slash, except * for HEAD in a detached state or MERGE_HEAD if we're in the * middle of a merge */ - if (is_oid_ref && + if (!(flags & GIT_REF_FORMAT_ALLOW_ONELEVEL) && !contains_a_slash && strcmp(name, GIT_HEAD_FILE) != 0 && strcmp(name, GIT_MERGE_HEAD_FILE) != 0 && @@ -1658,18 +1674,12 @@ static int normalize_name( *buffer_out = '\0'; - /* - * For object id references, name has to start with refs/. Again, - * we need to allow HEAD to be in a detached state. - */ - if (is_oid_ref && !(git__prefixcmp(buffer_out_start, GIT_REFS_DIR) || - strcmp(buffer_out_start, GIT_HEAD_FILE))) - goto invalid_name; - return 0; invalid_name: - giterr_set(GITERR_REFERENCE, "The given reference name is not valid"); + giterr_set( + GITERR_REFERENCE, + "The given reference name '%s' is not valid", name); return -1; } @@ -1678,7 +1688,11 @@ int git_reference__normalize_name( size_t out_size, const char *name) { - return normalize_name(buffer_out, out_size, name, 0); + return git_reference_normalize_name( + buffer_out, + out_size, + name, + GIT_REF_FORMAT_ALLOW_ONELEVEL); } int git_reference__normalize_name_oid( @@ -1686,7 +1700,11 @@ int git_reference__normalize_name_oid( size_t out_size, const char *name) { - return normalize_name(buffer_out, out_size, name, 1); + return git_reference_normalize_name( + buffer_out, + out_size, + name, + GIT_REF_FORMAT_NORMAL); } #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) @@ -1801,3 +1819,79 @@ int git_reference_foreach_glob( return git_reference_foreach( repo, list_flags, fromglob_cb, &data); } + +int git_reference_has_log( + git_reference *ref) +{ + git_buf path = GIT_BUF_INIT; + int result; + + assert(ref); + + if (git_buf_join_n(&path, '/', 3, ref->owner->path_repository, GIT_REFLOG_DIR, ref->name) < 0) + return -1; + + result = git_path_isfile(git_buf_cstr(&path)); + git_buf_free(&path); + + return result; +} + +int git_reference_is_branch(git_reference *ref) +{ + assert(ref); + return git__prefixcmp(ref->name, GIT_REFS_HEADS_DIR) == 0; +} + +int git_reference_is_remote(git_reference *ref) +{ + assert(ref); + return git__prefixcmp(ref->name, GIT_REFS_REMOTES_DIR) == 0; +} + +static int peel_error(int error, git_reference *ref, const char* msg) +{ + giterr_set( + GITERR_INVALID, + "The reference '%s' cannot be peeled - %s", git_reference_name(ref), msg); + return error; +} + +static int reference_target(git_object **object, git_reference *ref) +{ + const git_oid *oid; + + oid = git_reference_oid(ref); + + return git_object_lookup(object, git_reference_owner(ref), oid, GIT_OBJ_ANY); +} + +int git_reference_peel( + git_object **peeled, + git_reference *ref, + git_otype target_type) +{ + git_reference *resolved = NULL; + git_object *target = NULL; + int error; + + assert(ref); + + if ((error = git_reference_resolve(&resolved, ref)) < 0) + return peel_error(error, ref, "Cannot resolve reference"); + + if ((error = reference_target(&target, resolved)) < 0) { + 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 + error = git_object_peel(peeled, target, target_type); + +cleanup: + git_object_free(target); + git_reference_free(resolved); + return error; +} diff --git a/src/remote.c b/src/remote.c index 00e108a0a3b..7bc631d45f4 100644 --- a/src/remote.c +++ b/src/remote.c @@ -5,15 +5,16 @@ * a Linking Exception. For full terms see the included COPYING file. */ -#include "git2/remote.h" #include "git2/config.h" #include "git2/types.h" +#include "git2/oid.h" #include "config.h" #include "repository.h" #include "remote.h" #include "fetch.h" #include "refs.h" +#include "pkt.h" #include @@ -130,6 +131,26 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) remote->url = git__strdup(val); GITERR_CHECK_ALLOC(remote->url); + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", name) < 0) { + error = -1; + goto cleanup; + } + + error = git_config_get_string(&val, config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) + error = 0; + + if (error < 0) { + error = -1; + goto cleanup; + } + + if (val) { + remote->pushurl = git__strdup(val); + GITERR_CHECK_ALLOC(remote->pushurl); + } + git_buf_clear(&buf); if (git_buf_printf(&buf, "remote.%s.fetch", name) < 0) { error = -1; @@ -179,7 +200,7 @@ int git_remote_save(const git_remote *remote) if (git_repository_config__weakptr(&config, remote->repo) < 0) return -1; - if (git_buf_printf(&buf, "remote.%s.%s", remote->name, "url") < 0) + if (git_buf_printf(&buf, "remote.%s.url", remote->name) < 0) return -1; if (git_config_set_string(config, git_buf_cstr(&buf), remote->url) < 0) { @@ -187,6 +208,26 @@ int git_remote_save(const git_remote *remote) return -1; } + git_buf_clear(&buf); + if (git_buf_printf(&buf, "remote.%s.pushurl", remote->name) < 0) + return -1; + + if (remote->pushurl) { + if (git_config_set_string(config, git_buf_cstr(&buf), remote->pushurl) < 0) { + git_buf_free(&buf); + return -1; + } + } else { + int error = git_config_delete(config, git_buf_cstr(&buf)); + if (error == GIT_ENOTFOUND) { + error = 0; + } + if (error < 0) { + git_buf_free(&buf); + return -1; + } + } + if (remote->fetch.src != NULL && remote->fetch.dst != NULL) { git_buf_clear(&buf); git_buf_clear(&value); @@ -238,6 +279,38 @@ const char *git_remote_url(git_remote *remote) return remote->url; } +int git_remote_set_url(git_remote *remote, const char* url) +{ + assert(remote); + assert(url); + + git__free(remote->url); + remote->url = git__strdup(url); + GITERR_CHECK_ALLOC(remote->url); + + return 0; +} + +const char *git_remote_pushurl(git_remote *remote) +{ + assert(remote); + return remote->pushurl; +} + +int git_remote_set_pushurl(git_remote *remote, const char* url) +{ + assert(remote); + + git__free(remote->pushurl); + if (url) { + remote->pushurl = git__strdup(url); + GITERR_CHECK_ALLOC(remote->pushurl); + } else { + remote->pushurl = NULL; + } + return 0; +} + int git_remote_set_fetchspec(git_remote *remote, const char *spec) { git_refspec refspec; @@ -284,15 +357,38 @@ const git_refspec *git_remote_pushspec(git_remote *remote) return &remote->push; } +const char* git_remote__urlfordirection(git_remote *remote, int direction) +{ + assert(remote); + + if (direction == GIT_DIR_FETCH) { + return remote->url; + } + + if (direction == GIT_DIR_PUSH) { + return remote->pushurl ? remote->pushurl : remote->url; + } + + return NULL; +} + int git_remote_connect(git_remote *remote, int direction) { git_transport *t; + const char *url; assert(remote); - if (git_transport_new(&t, remote->url) < 0) + url = git_remote__urlfordirection(remote, direction); + if (url == NULL ) + return -1; + + if (git_transport_new(&t, url) < 0) return -1; + t->progress_cb = remote->callbacks.progress; + t->cb_data = remote->callbacks.data; + t->check_cert = remote->check_cert; if (t->connect(t, direction) < 0) { goto on_error; @@ -309,6 +405,10 @@ int git_remote_connect(git_remote *remote, int direction) 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) { @@ -316,7 +416,19 @@ int git_remote_ls(git_remote *remote, git_headlist_cb list_cb, void *payload) return -1; } - return remote->transport->ls(remote->transport, list_cb, payload); + git_vector_foreach(refs, i, p) { + git_pkt_ref *pkt = NULL; + + if (p->type != GIT_PKT_REF) + continue; + + pkt = (git_pkt_ref *)p; + + if (list_cb(&pkt->head, payload)) + return GIT_EUSER; + } + + return 0; } int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats *stats) @@ -331,7 +443,7 @@ int git_remote_download(git_remote *remote, git_off_t *bytes, git_indexer_stats return git_fetch_download_pack(remote, bytes, stats); } -int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, const git_oid *a, const git_oid *b)) +int git_remote_update_tips(git_remote *remote) { int error = 0; unsigned int i = 0; @@ -377,12 +489,12 @@ int git_remote_update_tips(git_remote *remote, int (*cb)(const char *refname, co continue; if (git_reference_create_oid(&ref, remote->repo, refname.ptr, &head->oid, 1) < 0) - break; + goto on_error; git_reference_free(ref); - if (cb != NULL) { - if (cb(refname.ptr, &old, &head->oid) < 0) + if (remote->callbacks.update_tips != NULL) { + if (remote->callbacks.update_tips(refname.ptr, &old, &head->oid, remote->callbacks.data) < 0) goto on_error; } } @@ -429,6 +541,7 @@ void git_remote_free(git_remote *remote) git__free(remote->push.src); git__free(remote->push.dst); git__free(remote->url); + git__free(remote->pushurl); git__free(remote->name); git__free(remote); } @@ -487,6 +600,11 @@ int git_remote_list(git_strarray *remotes_list, git_repository *repo) } git_vector_free(&list); + + /* cb error is converted to GIT_EUSER by git_config_foreach */ + if (error == GIT_EUSER) + error = -1; + return error; } @@ -525,3 +643,15 @@ void git_remote_check_cert(git_remote *remote, int check) remote->check_cert = check; } + +void git_remote_set_callbacks(git_remote *remote, git_remote_callbacks *callbacks) +{ + assert(remote && callbacks); + + memcpy(&remote->callbacks, callbacks, sizeof(git_remote_callbacks)); + + if (remote->transport) { + remote->transport->progress_cb = remote->callbacks.progress; + remote->transport->cb_data = remote->callbacks.data; + } +} diff --git a/src/remote.h b/src/remote.h index 0949ad4342d..67933a327cb 100644 --- a/src/remote.h +++ b/src/remote.h @@ -7,6 +7,8 @@ #ifndef INCLUDE_remote_h__ #define INCLUDE_remote_h__ +#include "git2/remote.h" + #include "refspec.h" #include "transport.h" #include "repository.h" @@ -14,13 +16,17 @@ struct git_remote { char *name; char *url; + char *pushurl; git_vector refs; struct git_refspec fetch; struct git_refspec push; git_transport *transport; git_repository *repo; + git_remote_callbacks callbacks; unsigned int need_pack:1, check_cert; }; +const char* git_remote__urlfordirection(struct git_remote *remote, int direction); + #endif diff --git a/src/repo_template.h b/src/repo_template.h new file mode 100644 index 00000000000..ae5a9690c20 --- /dev/null +++ b/src/repo_template.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_repo_template_h__ +#define INCLUDE_repo_template_h__ + +#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" +#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" + +#define GIT_HOOKS_DIR "hooks/" +#define GIT_HOOKS_DIR_MODE 0755 + +#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" +#define GIT_HOOKS_README_MODE 0755 +#define GIT_HOOKS_README_CONTENT \ +"#!/bin/sh\n"\ +"#\n"\ +"# Place appropriately named executable hook scripts into this directory\n"\ +"# to intercept various actions that git takes. See `git help hooks` for\n"\ +"# more information.\n" + +#define GIT_INFO_DIR "info/" +#define GIT_INFO_DIR_MODE 0755 + +#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" +#define GIT_INFO_EXCLUDE_MODE 0644 +#define GIT_INFO_EXCLUDE_CONTENT \ +"# File patterns to ignore; see `git help ignore` for more information.\n"\ +"# Lines that start with '#' are comments.\n" + +#define GIT_DESC_FILE "description" +#define GIT_DESC_MODE 0644 +#define GIT_DESC_CONTENT \ +"Unnamed repository; edit this file 'description' to name the repository.\n" + +typedef struct { + const char *path; + mode_t mode; + const char *content; +} repo_template_item; + +static repo_template_item repo_template[] = { + { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/info/' */ + { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE, NULL }, /* '/objects/pack/' */ + { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/heads/' */ + { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE, NULL }, /* '/refs/tags/' */ + { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE, NULL }, /* '/hooks/' */ + { GIT_INFO_DIR, GIT_INFO_DIR_MODE, NULL }, /* '/info/' */ + { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, + { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, + { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, + { NULL, 0, NULL } +}; + +#endif diff --git a/src/repository.c b/src/repository.c index bee7b3ee630..1a46db0a514 100644 --- a/src/repository.c +++ b/src/repository.c @@ -17,9 +17,8 @@ #include "fileops.h" #include "config.h" #include "refs.h" - -#define GIT_OBJECTS_INFO_DIR GIT_OBJECTS_DIR "info/" -#define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" +#include "filter.h" +#include "odb.h" #define GIT_FILE_CONTENT_PREFIX "gitdir:" @@ -27,6 +26,8 @@ #define GIT_REPO_VERSION 0 +#define GIT_TEMPLATE_DIR "/usr/share/git-core/templates" + static void drop_odb(git_repository *repo) { if (repo->_odb != NULL) { @@ -147,8 +148,13 @@ static int load_workdir(git_repository *repo, git_buf *parent_path) return -1; error = git_config_get_string(&worktree, config, "core.worktree"); - if (!error && worktree != NULL) - repo->workdir = git__strdup(worktree); + if (!error && worktree != NULL) { + error = git_path_prettify_dir( + &worktree_buf, worktree, repo->path_repository); + if (error < 0) + return error; + repo->workdir = git_buf_detach(&worktree_buf); + } else if (error != GIT_ENOTFOUND) return error; else { @@ -238,16 +244,17 @@ static int read_gitfile(git_buf *path_out, const char *file_path) git_buf_rtrim(&file); - if (file.size <= prefix_len || - memcmp(file.ptr, GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) + if (git_buf_len(&file) <= prefix_len || + memcmp(git_buf_cstr(&file), GIT_FILE_CONTENT_PREFIX, prefix_len) != 0) { giterr_set(GITERR_REPOSITORY, "The `.git` file at '%s' is malformed", file_path); error = -1; } else if ((error = git_path_dirname_r(path_out, file_path)) >= 0) { - const char *gitlink = ((const char *)file.ptr) + prefix_len; + const char *gitlink = git_buf_cstr(&file) + prefix_len; while (*gitlink && git__isspace(*gitlink)) gitlink++; - error = git_path_prettify_dir(path_out, gitlink, path_out->ptr); + error = git_path_prettify_dir( + path_out, gitlink, git_buf_cstr(path_out)); } git_buf_free(&file); @@ -359,9 +366,11 @@ int git_repository_open_ext( git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; git_repository *repo; - *repo_ptr = NULL; + if (repo_ptr) + *repo_ptr = NULL; - if ((error = find_repo(&path, &parent, start_path, flags, ceiling_dirs)) < 0) + error = find_repo(&path, &parent, start_path, flags, ceiling_dirs); + if (error < 0 || !repo_ptr) return error; repo = repository_alloc(); @@ -388,6 +397,19 @@ int git_repository_open(git_repository **repo_out, const char *path) repo_out, path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL); } +int git_repository_wrap_odb(git_repository **repo_out, git_odb *odb) +{ + git_repository *repo; + + repo = repository_alloc(); + GITERR_CHECK_ALLOC(repo); + + git_repository_set_odb(repo, odb); + *repo_out = repo; + + return 0; +} + int git_repository_discover( char *repository_path, size_t size, @@ -408,7 +430,7 @@ int git_repository_discover( if (size < (size_t)(path.size + 1)) { giterr_set(GITERR_REPOSITORY, - "The given buffer is too long to store the discovered path"); + "The given buffer is too small to store the discovered path"); git_buf_free(&path); return -1; } @@ -619,19 +641,35 @@ static int check_repositoryformatversion(git_config *config) return 0; } -static int repo_init_createhead(const char *git_dir) +static int repo_init_create_head(const char *git_dir, const char *ref_name) { git_buf ref_path = GIT_BUF_INIT; git_filebuf ref = GIT_FILEBUF_INIT; + const char *fmt; if (git_buf_joinpath(&ref_path, git_dir, GIT_HEAD_FILE) < 0 || - git_filebuf_open(&ref, ref_path.ptr, 0) < 0 || - git_filebuf_printf(&ref, "ref: refs/heads/master\n") < 0 || + git_filebuf_open(&ref, ref_path.ptr, 0) < 0) + goto fail; + + if (!ref_name) + ref_name = GIT_BRANCH_MASTER; + + if (git__prefixcmp(ref_name, "refs/") == 0) + fmt = "ref: %s\n"; + else + fmt = "ref: refs/heads/%s\n"; + + if (git_filebuf_printf(&ref, fmt, ref_name) < 0 || git_filebuf_commit(&ref, GIT_REFS_FILE_MODE) < 0) - return -1; + goto fail; git_buf_free(&ref_path); return 0; + +fail: + git_buf_free(&ref_path); + git_filebuf_cleanup(&ref); + return -1; } static bool is_chmod_supported(const char *file_path) @@ -652,6 +690,7 @@ static bool is_chmod_supported(const char *file_path) return false; _is_supported = (st1.st_mode != st2.st_mode); + return _is_supported; } @@ -673,19 +712,45 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path) return _is_insensitive; } -static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) +static bool are_symlinks_supported(const char *wd_path) { + git_buf path = GIT_BUF_INIT; + int fd; + struct stat st; + static int _symlinks_supported = -1; + + if (_symlinks_supported > -1) + return _symlinks_supported; + + if ((fd = git_futils_mktmp(&path, wd_path)) < 0 || + p_close(fd) < 0 || + p_unlink(path.ptr) < 0 || + p_symlink("testing", path.ptr) < 0 || + p_lstat(path.ptr, &st) < 0) + _symlinks_supported = false; + else + _symlinks_supported = (S_ISLNK(st.st_mode) != 0); + + (void)p_unlink(path.ptr); + git_buf_free(&path); + + return _symlinks_supported; +} + +static int repo_init_config( + const char *repo_dir, + const char *work_dir, + git_repository_init_options *opts) +{ + int error = 0; git_buf cfg_path = GIT_BUF_INIT; git_config *config = NULL; -#define SET_REPO_CONFIG(type, name, val) {\ - if (git_config_set_##type(config, name, val) < 0) { \ - git_buf_free(&cfg_path); \ - git_config_free(config); \ - return -1; } \ -} +#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\ + if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \ + goto cleanup; } while (0) - if (git_buf_joinpath(&cfg_path, git_dir, GIT_CONFIG_FILENAME_INREPO) < 0) + if (git_buf_joinpath(&cfg_path, repo_dir, GIT_CONFIG_FILENAME_INREPO) < 0) return -1; if (git_config_open_ondisk(&config, git_buf_cstr(&cfg_path)) < 0) { @@ -693,63 +758,75 @@ static int repo_init_config(const char *git_dir, bool is_bare, bool is_reinit) return -1; } - if (is_reinit && check_repositoryformatversion(config) < 0) { - git_buf_free(&cfg_path); - git_config_free(config); - return -1; - } + if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0 && + (error = check_repositoryformatversion(config)) < 0) + goto cleanup; - 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_bare) - SET_REPO_CONFIG(bool, "core.logallrefupdates", true); + SET_REPO_CONFIG( + bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); + SET_REPO_CONFIG( + int32, "core.repositoryformatversion", GIT_REPO_VERSION); + SET_REPO_CONFIG( + bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path))); - if (!is_reinit && is_filesystem_case_insensitive(git_dir)) - SET_REPO_CONFIG(bool, "core.ignorecase", true); - /* TODO: what other defaults? */ + if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) { + SET_REPO_CONFIG(bool, "core.logallrefupdates", true); - git_buf_free(&cfg_path); - git_config_free(config); - return 0; -} + if (!are_symlinks_supported(work_dir)) + SET_REPO_CONFIG(bool, "core.symlinks", false); -#define GIT_HOOKS_DIR "hooks/" -#define GIT_HOOKS_DIR_MODE 0755 + if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) { + SET_REPO_CONFIG(string, "core.worktree", work_dir); + } + else if ((opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) != 0) { + if (git_config_delete(config, "core.worktree") < 0) + giterr_clear(); + } + } else { + if (!are_symlinks_supported(repo_dir)) + SET_REPO_CONFIG(bool, "core.symlinks", false); + } -#define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" -#define GIT_HOOKS_README_MODE 0755 -#define GIT_HOOKS_README_CONTENT \ -"#!/bin/sh\n"\ -"#\n"\ -"# Place appropriately named executable hook scripts into this directory\n"\ -"# to intercept various actions that git takes. See `git help hooks` for\n"\ -"# more information.\n" + if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) && + is_filesystem_case_insensitive(repo_dir)) + SET_REPO_CONFIG(bool, "core.ignorecase", true); -#define GIT_INFO_DIR "info/" -#define GIT_INFO_DIR_MODE 0755 + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 1); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } + else if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) { + SET_REPO_CONFIG(int32, "core.sharedrepository", 2); + SET_REPO_CONFIG(bool, "receive.denyNonFastforwards", true); + } -#define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" -#define GIT_INFO_EXCLUDE_MODE 0644 -#define GIT_INFO_EXCLUDE_CONTENT \ -"# File patterns to ignore; see `git help ignore` for more information.\n"\ -"# Lines that start with '#' are comments.\n" +cleanup: + git_buf_free(&cfg_path); + git_config_free(config); -#define GIT_DESC_FILE "description" -#define GIT_DESC_MODE 0644 -#define GIT_DESC_CONTENT "Unnamed repository; edit this file 'description' to name the repository.\n" + return error; +} static int repo_write_template( - const char *git_dir, const char *file, mode_t mode, const char *content) + const char *git_dir, + bool allow_overwrite, + const char *file, + mode_t mode, + bool hidden, + const char *content) { git_buf path = GIT_BUF_INIT; - int fd, error = 0; + int fd, error = 0, flags; if (git_buf_joinpath(&path, git_dir, file) < 0) return -1; - fd = p_open(git_buf_cstr(&path), O_WRONLY | O_CREAT | O_EXCL, mode); + if (allow_overwrite) + flags = O_WRONLY | O_CREAT | O_TRUNC; + else + flags = O_WRONLY | O_CREAT | O_EXCL; + + fd = p_open(git_buf_cstr(&path), flags, mode); if (fd >= 0) { error = p_write(fd, content, strlen(content)); @@ -759,6 +836,15 @@ static int repo_write_template( else if (errno != EEXIST) error = fd; +#ifdef GIT_WIN32 + if (!error && hidden) { + if (p_hide_directory__w32(path.ptr) < 0) + error = -1; + } +#else + GIT_UNUSED(hidden); +#endif + git_buf_free(&path); if (error) @@ -768,86 +854,319 @@ static int repo_write_template( return error; } -static int repo_init_structure(const char *git_dir, int is_bare) -{ - int i; - struct { const char *dir; mode_t mode; } dirs[] = { - { GIT_OBJECTS_INFO_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/info/' */ - { GIT_OBJECTS_PACK_DIR, GIT_OBJECT_DIR_MODE }, /* '/objects/pack/' */ - { GIT_REFS_HEADS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/heads/' */ - { GIT_REFS_TAGS_DIR, GIT_REFS_DIR_MODE }, /* '/refs/tags/' */ - { GIT_HOOKS_DIR, GIT_HOOKS_DIR_MODE }, /* '/hooks/' */ - { GIT_INFO_DIR, GIT_INFO_DIR_MODE }, /* '/info/' */ - { NULL, 0 } - }; - struct { const char *file; mode_t mode; const char *content; } tmpl[] = { - { GIT_DESC_FILE, GIT_DESC_MODE, GIT_DESC_CONTENT }, - { GIT_HOOKS_README_FILE, GIT_HOOKS_README_MODE, GIT_HOOKS_README_CONTENT }, - { GIT_INFO_EXCLUDE_FILE, GIT_INFO_EXCLUDE_MODE, GIT_INFO_EXCLUDE_CONTENT }, - { NULL, 0, NULL } - }; - - /* Make the base directory */ - if (git_futils_mkdir_r(git_dir, NULL, is_bare ? GIT_BARE_DIR_MODE : GIT_DIR_MODE) < 0) +static int repo_write_gitlink( + const char *in_dir, const char *to_repo) +{ + int error; + git_buf buf = GIT_BUF_INIT; + struct stat st; + + git_path_dirname_r(&buf, to_repo); + git_path_to_dir(&buf); + if (git_buf_oom(&buf)) return -1; - /* Hides the ".git" directory */ - if (!is_bare) { + /* don't write gitlink to natural workdir */ + if (git__suffixcmp(to_repo, "/" DOT_GIT "/") == 0 && + strcmp(in_dir, buf.ptr) == 0) + { + error = GIT_PASSTHROUGH; + goto cleanup; + } + + if ((error = git_buf_joinpath(&buf, in_dir, DOT_GIT)) < 0) + goto cleanup; + + if (!p_stat(buf.ptr, &st) && !S_ISREG(st.st_mode)) { + giterr_set(GITERR_REPOSITORY, + "Cannot overwrite gitlink file into path '%s'", in_dir); + error = GIT_EEXISTS; + goto cleanup; + } + + git_buf_clear(&buf); + + error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo); + + if (!error) + error = repo_write_template(in_dir, true, DOT_GIT, 0644, true, buf.ptr); + +cleanup: + git_buf_free(&buf); + return error; +} + +static mode_t pick_dir_mode(git_repository_init_options *opts) +{ + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) + return 0755; + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) + return (0775 | S_ISGID); + if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) + return (0777 | S_ISGID); + return opts->mode; +} + +#include "repo_template.h" + +static int repo_init_structure( + const char *repo_dir, + const char *work_dir, + git_repository_init_options *opts) +{ + int error = 0; + repo_template_item *tpl; + bool external_tpl = + ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); + mode_t dmode = pick_dir_mode(opts); + + /* Hide the ".git" directory */ #ifdef GIT_WIN32 - if (p_hide_directory__w32(git_dir) < 0) { + if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { + if (p_hide_directory__w32(repo_dir) < 0) { giterr_set(GITERR_REPOSITORY, "Failed to mark Git repository folder as hidden"); return -1; } -#endif } +#endif - /* Make subdirectories as needed */ - for (i = 0; dirs[i].dir != NULL; ++i) { - if (git_futils_mkdir_r(dirs[i].dir, git_dir, dirs[i].mode) < 0) + /* Create the .git gitlink if appropriate */ + if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && + (opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD) == 0) + { + if (repo_write_gitlink(work_dir, repo_dir) < 0) return -1; } - /* Make template files as needed */ - for (i = 0; tmpl[i].file != NULL; ++i) { - if (repo_write_template( - git_dir, tmpl[i].file, tmpl[i].mode, tmpl[i].content) < 0) + /* Copy external template if requested */ + if (external_tpl) { + git_config *cfg; + const char *tdir; + + if (opts->template_path) + tdir = opts->template_path; + else if ((error = git_config_open_default(&cfg)) < 0) + return error; + else { + error = git_config_get_string(&tdir, cfg, "init.templatedir"); + + git_config_free(cfg); + + if (error && error != GIT_ENOTFOUND) + return error; + + giterr_clear(); + tdir = GIT_TEMPLATE_DIR; + } + + error = git_futils_cp_r(tdir, repo_dir, + GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD, dmode); + + if (error < 0) { + if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0) + return error; + + /* if template was default, ignore error and use internal */ + giterr_clear(); + external_tpl = false; + } + } + + /* Copy internal template + * - always ensure existence of dirs + * - only create files if no external template was specified + */ + for (tpl = repo_template; !error && tpl->path; ++tpl) { + if (!tpl->content) + error = git_futils_mkdir( + tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); + else if (!external_tpl) { + const char *content = tpl->content; + + if (opts->description && strcmp(tpl->path, GIT_DESC_FILE) == 0) + content = opts->description; + + error = repo_write_template( + repo_dir, false, tpl->path, tpl->mode, false, content); + } + } + + return error; +} + +static int repo_init_directories( + git_buf *repo_path, + git_buf *wd_path, + const char *given_repo, + git_repository_init_options *opts) +{ + int error = 0; + bool add_dotgit, has_dotgit, natural_wd; + mode_t dirmode; + + /* set up repo path */ + + add_dotgit = + (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && + (opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && + git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && + git__suffixcmp(given_repo, "/" GIT_DIR) != 0; + + if (git_buf_joinpath(repo_path, given_repo, add_dotgit ? GIT_DIR : "") < 0) + return -1; + + has_dotgit = (git__suffixcmp(repo_path->ptr, "/" GIT_DIR) == 0); + if (has_dotgit) + opts->flags |= GIT_REPOSITORY_INIT__HAS_DOTGIT; + + /* set up workdir path */ + + if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0) { + if (opts->workdir_path) { + if (git_path_join_unrooted( + wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) + return -1; + } else if (has_dotgit) { + if (git_path_dirname_r(wd_path, repo_path->ptr) < 0) + return -1; + } else { + giterr_set(GITERR_REPOSITORY, "Cannot pick working directory" + " for non-bare repository that isn't a '.git' directory"); return -1; + } + + if (git_path_to_dir(wd_path) < 0) + return -1; + } else { + git_buf_clear(wd_path); } - return 0; + natural_wd = + has_dotgit && + wd_path->size > 0 && + wd_path->size + strlen(GIT_DIR) == repo_path->size && + memcmp(repo_path->ptr, wd_path->ptr, wd_path->size) == 0; + if (natural_wd) + opts->flags |= GIT_REPOSITORY_INIT__NATURAL_WD; + + /* create directories as needed / requested */ + + dirmode = pick_dir_mode(opts); + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 && has_dotgit) { + git_buf p = GIT_BUF_INIT; + if ((error = git_path_dirname_r(&p, repo_path->ptr)) >= 0) + error = git_futils_mkdir(p.ptr, NULL, dirmode, 0); + git_buf_free(&p); + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || + has_dotgit) + { + uint32_t mkflag = GIT_MKDIR_CHMOD; + if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) + mkflag |= GIT_MKDIR_PATH; + error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, mkflag); + } + + if (wd_path->size > 0 && + !natural_wd && + ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)) + error = git_futils_mkdir(wd_path->ptr, NULL, dirmode & ~S_ISGID, + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) ? GIT_MKDIR_PATH : 0); + + /* prettify both directories now that they are created */ + + if (!error) { + error = git_path_prettify_dir(repo_path, repo_path->ptr, NULL); + + if (!error && wd_path->size > 0) + error = git_path_prettify_dir(wd_path, wd_path->ptr, NULL); + } + + return error; } -int git_repository_init(git_repository **repo_out, const char *path, unsigned is_bare) +static int repo_init_create_origin(git_repository *repo, const char *url) { - git_buf repository_path = GIT_BUF_INIT; - bool is_reinit; - int result = -1; + int error; + git_remote *remote; - assert(repo_out && path); + if (!(error = git_remote_add(&remote, repo, "origin", url))) { + error = git_remote_save(remote); + git_remote_free(remote); + } - if (git_buf_joinpath(&repository_path, path, is_bare ? "" : GIT_DIR) < 0) - goto cleanup; + return error; +} - is_reinit = git_path_isdir(repository_path.ptr) && valid_repository_path(&repository_path); +int git_repository_init( + git_repository **repo_out, const char *path, unsigned is_bare) +{ + git_repository_init_options opts; - if (is_reinit) { - /* TODO: reinitialize the templates */ + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_REPOSITORY_INIT_MKPATH; /* don't love this default */ + if (is_bare) + opts.flags |= GIT_REPOSITORY_INIT_BARE; - if (repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0) - goto cleanup; + return git_repository_init_ext(repo_out, path, &opts); +} + +int git_repository_init_ext( + git_repository **repo_out, + const char *given_repo, + git_repository_init_options *opts) +{ + int error; + git_buf repo_path = GIT_BUF_INIT, wd_path = GIT_BUF_INIT; + + assert(repo_out && given_repo && opts); - } else if (repo_init_structure(repository_path.ptr, is_bare) < 0 || - repo_init_config(repository_path.ptr, is_bare, is_reinit) < 0 || - repo_init_createhead(repository_path.ptr) < 0) { + error = repo_init_directories(&repo_path, &wd_path, given_repo, opts); + if (error < 0) goto cleanup; + + if (valid_repository_path(&repo_path)) { + + if ((opts->flags & GIT_REPOSITORY_INIT_NO_REINIT) != 0) { + giterr_set(GITERR_REPOSITORY, + "Attempt to reinitialize '%s'", given_repo); + error = GIT_EEXISTS; + goto cleanup; + } + + opts->flags |= GIT_REPOSITORY_INIT__IS_REINIT; + + error = repo_init_config( + git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts); + + /* TODO: reinitialize the templates */ + } + else { + if (!(error = repo_init_structure( + git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts)) && + !(error = repo_init_config( + git_buf_cstr(&repo_path), git_buf_cstr(&wd_path), opts))) + error = repo_init_create_head( + git_buf_cstr(&repo_path), opts->initial_head); } + if (error < 0) + goto cleanup; - result = git_repository_open(repo_out, repository_path.ptr); + error = git_repository_open(repo_out, git_buf_cstr(&repo_path)); + + if (!error && opts->origin_url) + error = repo_init_create_origin(*repo_out, opts->origin_url); cleanup: - git_buf_free(&repository_path); - return result; + git_buf_free(&repo_path); + git_buf_free(&wd_path); + + return error; } int git_repository_head_detached(git_repository *repo) @@ -943,8 +1262,10 @@ const char *git_repository_workdir(git_repository *repo) return repo->workdir; } -int git_repository_set_workdir(git_repository *repo, const char *workdir) +int git_repository_set_workdir( + git_repository *repo, const char *workdir, int update_gitlink) { + int error = 0; git_buf path = GIT_BUF_INIT; assert(repo && workdir); @@ -952,11 +1273,37 @@ int git_repository_set_workdir(git_repository *repo, const char *workdir) if (git_path_prettify_dir(&path, workdir, NULL) < 0) return -1; - git__free(repo->workdir); + if (repo->workdir && strcmp(repo->workdir, path.ptr) == 0) + return 0; - repo->workdir = git_buf_detach(&path); - repo->is_bare = 0; - return 0; + if (update_gitlink) { + git_config *config; + + if (git_repository_config__weakptr(&config, repo) < 0) + return -1; + + error = repo_write_gitlink(path.ptr, git_repository_path(repo)); + + /* passthrough error means gitlink is unnecessary */ + if (error == GIT_PASSTHROUGH) + error = git_config_delete(config, "core.worktree"); + else if (!error) + error = git_config_set_string(config, "core.worktree", path.ptr); + + if (!error) + error = git_config_set_bool(config, "core.bare", false); + } + + if (!error) { + char *old_workdir = repo->workdir; + + repo->workdir = git_buf_detach(&path); + repo->is_bare = 0; + + git__free(old_workdir); + } + + return error; } int git_repository_is_bare(git_repository *repo) @@ -984,3 +1331,197 @@ int git_repository_head_tree(git_tree **tree, git_repository *repo) *tree = (git_tree *)obj; 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) + return -1; + + if ((error = p_stat(git_buf_cstr(&path), &st)) < 0) { + if (errno == ENOENT) + error = GIT_ENOTFOUND; + } + else if (buffer != NULL) { + error = git_futils_readbuffer(&buf, git_buf_cstr(&path)); + git_buf_copy_cstr(buffer, len, &buf); + } + + git_buf_free(&path); + git_buf_free(&buf); + + if (!error) + error = (int)st.st_size + 1; /* add 1 for NUL byte */ + + return error; +} + +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) + return -1; + + error = p_unlink(git_buf_cstr(&path)); + git_buf_free(&path); + + return error; +} + +int git_repository_hashfile( + git_oid *out, + git_repository *repo, + const char *path, + git_otype type, + const char *as_path) +{ + int error; + git_vector filters = GIT_VECTOR_INIT; + git_file fd = -1; + git_off_t len; + git_buf full_path = GIT_BUF_INIT; + + assert(out && path && repo); /* as_path can be NULL */ + + /* At some point, it would be nice if repo could be NULL to just + * apply filter rules defined in system and global files, but for + * now that is not possible because git_filters_load() needs it. + */ + + error = git_path_join_unrooted( + &full_path, path, repo ? git_repository_workdir(repo) : NULL, NULL); + if (error < 0) + return error; + + if (!as_path) + as_path = path; + + /* passing empty string for "as_path" indicated --no-filters */ + if (strlen(as_path) > 0) { + error = git_filters_load(&filters, repo, as_path, GIT_FILTER_TO_ODB); + if (error < 0) + return error; + } else { + error = 0; + } + + /* at this point, error is a count of the number of loaded filters */ + + fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) { + error = fd; + goto cleanup; + } + + len = git_futils_filesize(fd); + if (len < 0) { + error = (int)len; + goto cleanup; + } + + if (!git__is_sizet(len)) { + giterr_set(GITERR_OS, "File size overflow for 32-bit systems"); + error = -1; + goto cleanup; + } + + error = git_odb__hashfd_filtered(out, fd, (size_t)len, type, &filters); + +cleanup: + if (fd >= 0) + p_close(fd); + git_filters_free(&filters); + git_buf_free(&full_path); + + return error; +} + +static bool looks_like_a_branch(const char *refname) +{ + return git__prefixcmp(refname, GIT_REFS_HEADS_DIR) == 0; +} + +int git_repository_set_head( + git_repository* repo, + const char* refname) +{ + git_reference *ref, + *new_head = NULL; + int error; + + assert(repo && refname); + + error = git_reference_lookup(&ref, repo, refname); + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + if (git_reference_is_branch(ref)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, git_reference_name(ref), 1); + else + error = git_repository_set_head_detached(repo, git_reference_oid(ref)); + } else if (looks_like_a_branch(refname)) + error = git_reference_create_symbolic(&new_head, repo, GIT_HEAD_FILE, refname, 1); + + git_reference_free(ref); + git_reference_free(new_head); + return error; +} + +int git_repository_set_head_detached( + git_repository* repo, + const git_oid* commitish) +{ + int error; + git_object *object, + *peeled = NULL; + git_reference *new_head = NULL; + + assert(repo && commitish); + + if ((error = git_object_lookup(&object, repo, commitish, GIT_OBJ_ANY)) < 0) + return error; + + if ((error = git_object_peel(&peeled, object, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_object_id(peeled), 1); + +cleanup: + git_object_free(object); + git_object_free(peeled); + git_reference_free(new_head); + return error; +} + +int git_repository_detach_head( + git_repository* repo) +{ + git_reference *old_head = NULL, + *new_head = NULL; + git_object *object = NULL; + int error = -1; + + assert(repo); + + if (git_repository_head(&old_head, repo) < 0) + return -1; + + if (git_object_lookup(&object, repo, git_reference_oid(old_head), GIT_OBJ_COMMIT) < 0) + goto cleanup; + + error = git_reference_create_oid(&new_head, repo, GIT_HEAD_FILE, git_reference_oid(old_head), 1); + +cleanup: + git_object_free(object); + git_reference_free(old_head); + git_reference_free(new_head); + return error; +} diff --git a/src/repository.h b/src/repository.h index 91c69a6558a..b919fd8140e 100644 --- a/src/repository.h +++ b/src/repository.h @@ -18,6 +18,7 @@ #include "refs.h" #include "buffer.h" #include "odb.h" +#include "object.h" #include "attr.h" #include "strmap.h" @@ -68,13 +69,14 @@ typedef enum { GIT_EOL_DEFAULT = GIT_EOL_NATIVE } git_cvar_value; -/** Base git object for inheritance */ -struct git_object { - git_cached_obj cached; - git_repository *repo; - git_otype type; +/* internal repository init flags */ +enum { + GIT_REPOSITORY_INIT__HAS_DOTGIT = (1u << 16), + GIT_REPOSITORY_INIT__NATURAL_WD = (1u << 17), + GIT_REPOSITORY_INIT__IS_REINIT = (1u << 18), }; +/** Internal structure for repository object */ struct git_repository { git_odb *_odb; git_config *_config; @@ -94,15 +96,6 @@ struct git_repository { git_cvar_value cvar_cache[GIT_CVAR_CACHE_MAX]; }; -/* fully free the object; internal method, do not - * export */ -void git_object__free(void *object); - -int git_object__resolve_to_type(git_object **obj, git_otype type); - -int git_oid__parse(git_oid *oid, const char **buffer_out, const char *buffer_end, const char *header); -void git_oid__writebuf(git_buf *buf, const char *header, const git_oid *oid); - GIT_INLINE(git_attr_cache *) git_repository_attr_cache(git_repository *repo) { return &repo->attrcache; @@ -122,7 +115,7 @@ int git_repository_odb__weakptr(git_odb **out, git_repository *repo); int git_repository_index__weakptr(git_index **out, git_repository *repo); /* - * CVAR cache + * CVAR cache * * Efficient access to the most used config variables of a repository. * The cache is cleared everytime the config backend is replaced. @@ -135,4 +128,19 @@ void git_repository__cvar_cache_clear(git_repository *repo); */ extern void git_submodule_config_free(git_repository *repo); +GIT_INLINE(int) git_repository__ensure_not_bare( + git_repository *repo, + const char *operation_name) +{ + if (!git_repository_is_bare(repo)) + return 0; + + giterr_set( + GITERR_REPOSITORY, + "Cannot %s. This operation is not allowed against bare repositories.", + operation_name); + + return GIT_EBAREREPO; +} + #endif diff --git a/src/reset.c b/src/reset.c index 14f7a236a48..4ce21e2cfc8 100644 --- a/src/reset.c +++ b/src/reset.c @@ -9,6 +9,7 @@ #include "commit.h" #include "tag.h" #include "git2/reset.h" +#include "git2/checkout.h" #define ERROR_MSG "Cannot perform reset" @@ -20,44 +21,30 @@ static int reset_error_invalid(const char *msg) int git_reset( git_repository *repo, - const git_object *target, + git_object *target, git_reset_type reset_type) { - git_otype target_type = GIT_OBJ_BAD; git_object *commit = NULL; git_index *index = NULL; git_tree *tree = NULL; int error = -1; + git_checkout_opts opts; assert(repo && target); - assert(reset_type == GIT_RESET_SOFT || reset_type == GIT_RESET_MIXED); + assert(reset_type == GIT_RESET_SOFT + || reset_type == GIT_RESET_MIXED + || reset_type == GIT_RESET_HARD); if (git_object_owner(target) != repo) return reset_error_invalid("The given target does not belong to this repository."); - if (reset_type == GIT_RESET_MIXED && git_repository_is_bare(repo)) - return reset_error_invalid("Mixed reset is not allowed in a bare repository."); + if (reset_type == GIT_RESET_MIXED + && git_repository__ensure_not_bare(repo, "reset mixed") < 0) + return GIT_EBAREREPO; - target_type = git_object_type(target); - - switch (target_type) - { - case GIT_OBJ_TAG: - if (git_tag_peel(&commit, (git_tag *)target) < 0) - goto cleanup; - - if (git_object_type(commit) != GIT_OBJ_COMMIT) { - reset_error_invalid("The given target does not resolve to a commit."); - goto cleanup; - } - break; - - case GIT_OBJ_COMMIT: - commit = (git_object *)target; - break; - - default: - return reset_error_invalid("Only git_tag and git_commit objects are valid targets."); + if (git_object_peel(&commit, target, GIT_OBJ_COMMIT) < 0) { + reset_error_invalid("The given target does not resolve to a commit"); + goto cleanup; } //TODO: Check for unmerged entries @@ -80,7 +67,7 @@ int git_reset( goto cleanup; } - if (git_index_read_tree(index, tree) < 0) { + if (git_index_read_tree(index, tree, NULL) < 0) { giterr_set(GITERR_INDEX, "%s - Failed to update the index.", ERROR_MSG); goto cleanup; } @@ -90,12 +77,26 @@ int git_reset( 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; + + if (git_checkout_index(repo, &opts, NULL) < 0) { + giterr_set(GITERR_INDEX, "%s - Failed to checkout the index.", ERROR_MSG); + goto cleanup; + } + error = 0; cleanup: - if (target_type == GIT_OBJ_TAG) - git_object_free(commit); - + git_object_free(commit); git_index_free(index); git_tree_free(tree); diff --git a/src/revparse.c b/src/revparse.c index 5050bdf1b11..17266b94485 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -13,52 +13,21 @@ #include "git2.h" -typedef enum { - REVPARSE_STATE_INIT, - REVPARSE_STATE_CARET, - REVPARSE_STATE_LINEAR, - REVPARSE_STATE_COLON, - REVPARSE_STATE_DONE, -} revparse_state; - -static void set_invalid_syntax_err(const char *spec) +static int revspec_error(const char *revspec) { - giterr_set(GITERR_INVALID, "Refspec '%s' is not valid.", spec); + giterr_set(GITERR_INVALID, "Failed to parse revision specifier - Invalid pattern '%s'", revspec); + return -1; } -static int revparse_lookup_fully_qualifed_ref(git_object **out, git_repository *repo, const char*spec) +static int disambiguate_refname(git_reference **out, git_repository *repo, const char *refname) { - git_oid resolved; - - if (git_reference_name_to_oid(&resolved, repo, spec) < 0) - return GIT_ERROR; - - return git_object_lookup(out, repo, &resolved, GIT_OBJ_ANY); -} - -/* Returns non-zero if yes */ -static int spec_looks_like_describe_output(const char *spec) -{ - regex_t regex; - int regex_error, retcode; - - regex_error = regcomp(®ex, ".+-[0-9]+-g[0-9a-fA-F]+", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - return 1; /* To be safe */ - } - retcode = regexec(®ex, spec, 0, NULL, 0); - regfree(®ex); - return retcode == 0; -} + int error, i; + bool fallbackmode = true; + git_reference *ref; + git_buf refnamebuf = GIT_BUF_INIT, name = GIT_BUF_INIT; -static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) -{ - size_t speclen = strlen(spec); - git_object *obj = NULL; - git_oid oid; - git_buf refnamebuf = GIT_BUF_INIT; static const char* formatters[] = { + "%s", "refs/%s", "refs/tags/%s", "refs/heads/%s", @@ -66,729 +35,796 @@ static int revparse_lookup_object(git_object **out, git_repository *repo, const "refs/remotes/%s/HEAD", NULL }; - unsigned int i; - const char *substr; - - /* "git describe" output; snip everything before/including "-g" */ - substr = strstr(spec, "-g"); - if (substr && - spec_looks_like_describe_output(spec) && - !revparse_lookup_object(out, repo, substr+2)) { - return 0; - } - /* SHA or prefix */ - if (!git_oid_fromstrn(&oid, spec, speclen)) { - if (!git_object_lookup_prefix(&obj, repo, &oid, speclen, GIT_OBJ_ANY)) { - *out = obj; - return 0; - } + if (*refname) + git_buf_puts(&name, refname); + else { + git_buf_puts(&name, GIT_HEAD_FILE); + fallbackmode = false; } - /* Fully-named ref */ - if (!revparse_lookup_fully_qualifed_ref(&obj, repo, spec)) { - *out = obj; - return 0; - } + for (i = 0; formatters[i] && (fallbackmode || i == 0); i++) { - /* Partially-named ref; match in this order: */ - for (i=0; formatters[i]; i++) { git_buf_clear(&refnamebuf); - if (git_buf_printf(&refnamebuf, formatters[i], spec) < 0) { - return GIT_ERROR; - } - if (!revparse_lookup_fully_qualifed_ref(&obj, repo, git_buf_cstr(&refnamebuf))) { - git_buf_free(&refnamebuf); - *out = obj; - return 0; + if ((error = git_buf_printf(&refnamebuf, formatters[i], git_buf_cstr(&name))) < 0) + goto cleanup; + + error = git_reference_lookup_resolved(&ref, repo, git_buf_cstr(&refnamebuf), -1); + + if (!error) { + *out = ref; + error = 0; + goto cleanup; } + + if (error != GIT_ENOTFOUND) + goto cleanup; } - git_buf_free(&refnamebuf); - giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); - return GIT_ERROR; +cleanup: + git_buf_free(&name); + git_buf_free(&refnamebuf); + return error; } +static int maybe_sha_or_abbrev(git_object**out, git_repository *repo, const char *spec) +{ + git_oid oid; + size_t speclen = strlen(spec); + + if (git_oid_fromstrn(&oid, spec, speclen) < 0) + return GIT_ENOTFOUND; + + return git_object_lookup_prefix(out, repo, &oid, speclen, GIT_OBJ_ANY); +} -static int all_chars_are_digits(const char *str, size_t len) +static int build_regex(regex_t *regex, const char *pattern) { - size_t i=0; - for (i=0; i '9') return 0; + int error; + + if (*pattern == '\0') { + giterr_set(GITERR_REGEX, "Empty pattern"); + return -1; } - return 1; + + error = regcomp(regex, pattern, REG_EXTENDED); + if (!error) + return 0; + + giterr_set_regex(regex, error); + regfree(regex); + + return -1; +} + +static int maybe_describe(git_object**out, git_repository *repo, const char *spec) +{ + const char *substr; + int error; + regex_t regex; + + substr = strstr(spec, "-g"); + + if (substr == NULL) + return GIT_ENOTFOUND; + + if (build_regex(®ex, ".+-[0-9]+-g[0-9a-fA-F]+") < 0) + return -1; + + error = regexec(®ex, spec, 0, NULL, 0); + regfree(®ex); + + if (error) + return GIT_ENOTFOUND; + + return maybe_sha_or_abbrev(out, repo, substr+2); } -static void normalize_maybe_empty_refname(git_buf *buf, git_repository *repo, const char *refspec, size_t refspeclen) +static int revparse_lookup_object(git_object **out, git_repository *repo, const char *spec) { + int error; git_reference *ref; - if (!refspeclen) { - /* Empty refspec means current branch (target of HEAD) */ - git_reference_lookup(&ref, repo, "HEAD"); - git_buf_puts(buf, git_reference_target(ref)); + error = maybe_describe(out, repo, spec); + if (!error) + return 0; + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + error = disambiguate_refname(&ref, repo, spec); + if (!error) { + error = git_object_lookup(out, repo, git_reference_oid(ref), GIT_OBJ_ANY); git_reference_free(ref); - } else if (strstr(refspec, "HEAD")) { - /* Explicit head */ - git_buf_puts(buf, refspec); - }else { - if (git__prefixcmp(refspec, "refs/heads/") != 0) { - git_buf_printf(buf, "refs/heads/%s", refspec); - } else { - git_buf_puts(buf, refspec); - } + return error; } + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + error = maybe_sha_or_abbrev(out, repo, spec); + if (!error) + return 0; + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + giterr_set(GITERR_REFERENCE, "Refspec '%s' not found.", spec); + return GIT_ENOTFOUND; } -static int walk_ref_history(git_object **out, git_repository *repo, const char *refspec, const char *reflogspec) +static int try_parse_numeric(int *n, const char *curly_braces_content) { - git_reference *ref; + int content; + const char *end_ptr; + + if (git__strtol32(&content, curly_braces_content, &end_ptr, 10) < 0) + return -1; + + if (*end_ptr != '\0') + return -1; + + *n = content; + return 0; +} + +static int retrieve_previously_checked_out_branch_or_revision(git_object **out, git_reference **base_ref, git_repository *repo, const char *spec, const char *identifier, unsigned int position) +{ + git_reference *ref = NULL; git_reflog *reflog = NULL; - int n, retcode = GIT_ERROR; - int i, refloglen; + regex_t preg; + int numentries, i, cur, error = -1; const git_reflog_entry *entry; + const char *msg; + regmatch_t regexmatches[2]; git_buf buf = GIT_BUF_INIT; - size_t refspeclen = strlen(refspec); - size_t reflogspeclen = strlen(reflogspec); - if (git__prefixcmp(reflogspec, "@{") != 0 || - git__suffixcmp(reflogspec, "}") != 0) { - giterr_set(GITERR_INVALID, "Bad reflogspec '%s'", reflogspec); - return GIT_ERROR; - } + cur = position; - /* "@{-N}" form means walk back N checkouts. That means the HEAD log. */ - if (refspeclen == 0 && !git__prefixcmp(reflogspec, "@{-")) { - regex_t regex; - int regex_error; + if (*identifier != '\0' || *base_ref != NULL) + return revspec_error(spec); - if (git__strtol32(&n, reflogspec+3, NULL, 0) < 0 || - n < 1) { - giterr_set(GITERR_INVALID, "Invalid reflogspec %s", reflogspec); - return GIT_ERROR; - } + if (build_regex(&preg, "checkout: moving from (.*) to .*") < 0) + return -1; - if (!git_reference_lookup(&ref, repo, "HEAD")) { - if (!git_reflog_read(&reflog, ref)) { - regex_error = regcomp(®ex, "checkout: moving from (.*) to .*", REG_EXTENDED); - if (regex_error != 0) { - giterr_set_regex(®ex, regex_error); - } else { - regmatch_t regexmatches[2]; - - refloglen = git_reflog_entrycount(reflog); - for (i=refloglen-1; i >= 0; i--) { - const char *msg; - entry = git_reflog_entry_byindex(reflog, i); - - msg = git_reflog_entry_msg(entry); - if (!regexec(®ex, msg, 2, regexmatches, 0)) { - n--; - if (!n) { - git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); - retcode = revparse_lookup_object(out, repo, git_buf_cstr(&buf)); - break; - } - } - } - regfree(®ex); - } - } - git_reference_free(ref); - } - } else { - int date_error = 0; - git_time_t timestamp; - git_buf datebuf = GIT_BUF_INIT; - - git_buf_put(&datebuf, reflogspec+2, reflogspeclen-3); - date_error = git__date_parse(×tamp, git_buf_cstr(&datebuf)); - - /* @{u} or @{upstream} -> upstream branch, for a tracking branch. This is stored in the config. */ - if (!strcmp(reflogspec, "@{u}") || !strcmp(reflogspec, "@{upstream}")) { - git_config *cfg; - if (!git_repository_config(&cfg, repo)) { - /* Is the ref a tracking branch? */ - const char *remote; - git_buf_clear(&buf); - git_buf_printf(&buf, "branch.%s.remote", refspec); - if (!git_config_get_string(&remote, cfg, git_buf_cstr(&buf))) { - /* Yes. Find the first merge target name. */ - const char *mergetarget; - git_buf_clear(&buf); - git_buf_printf(&buf, "branch.%s.merge", refspec); - if (!git_config_get_string(&mergetarget, cfg, git_buf_cstr(&buf)) && - !git__prefixcmp(mergetarget, "refs/heads/")) { - /* Success. Look up the target and fetch the object. */ - git_buf_clear(&buf); - git_buf_printf(&buf, "refs/remotes/%s/%s", remote, mergetarget+11); - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); - } - } - git_config_free(cfg); - } - } + if (git_reference_lookup(&ref, repo, GIT_HEAD_FILE) < 0) + goto cleanup; - /* @{N} -> Nth prior value for the ref (from reflog) */ - else if (all_chars_are_digits(reflogspec+2, reflogspeclen-3) && - !git__strtol32(&n, reflogspec+2, NULL, 0) && - n <= 100000000) { /* Allow integer time */ - normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen); - - if (n == 0) { - retcode = revparse_lookup_fully_qualifed_ref(out, repo, git_buf_cstr(&buf)); - } else if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) { - if (!git_reflog_read(&reflog, ref)) { - int numentries = git_reflog_entrycount(reflog); - if (numentries < n) { - giterr_set(GITERR_REFERENCE, "Reflog for '%s' has only %d entries, asked for %d", - git_buf_cstr(&buf), numentries, n); - retcode = GIT_ERROR; - } else { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, n); - const git_oid *oid = git_reflog_entry_oidold(entry); - retcode = git_object_lookup(out, repo, oid, GIT_OBJ_ANY); - } - } - git_reference_free(ref); - } - } + if (git_reflog_read(&reflog, ref) < 0) + goto cleanup; - else if (!date_error) { - /* Ref as it was on a certain date */ - normalize_maybe_empty_refname(&buf, repo, refspec, refspeclen); - - if (!git_reference_lookup(&ref, repo, git_buf_cstr(&buf))) { - git_reflog *reflog; - if (!git_reflog_read(&reflog, ref)) { - /* Keep walking until we find an entry older than the given date */ - int numentries = git_reflog_entrycount(reflog); - int i; - - /* TODO: clunky. Factor "now" into a utility */ - git_signature *sig; - git_time as_of; - - git_signature_now(&sig, "blah", "blah"); - as_of = sig->when; - git_signature_free(sig); - - as_of.time = (timestamp > 0) - ? timestamp - : sig->when.time + timestamp; - - for (i=numentries-1; i>0; i--) { - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, i); - git_time commit_time = git_reflog_entry_committer(entry)->when; - if (git__time_cmp(&commit_time, &as_of) <= 0 ) { - retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); - break; - } - } - - if (!i) { - /* Didn't find a match. Use the oldest revision in the reflog. */ - const git_reflog_entry *entry = git_reflog_entry_byindex(reflog, 0); - retcode = git_object_lookup(out, repo, git_reflog_entry_oidnew(entry), GIT_OBJ_ANY); - } - - git_reflog_free(reflog); - } + numentries = git_reflog_entrycount(reflog); - git_reference_free(ref); - } - } + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + msg = git_reflog_entry_msg(entry); + + if (regexec(&preg, msg, 2, regexmatches, 0)) + continue; + + cur--; + + if (cur > 0) + continue; + + git_buf_put(&buf, msg+regexmatches[1].rm_so, regexmatches[1].rm_eo - regexmatches[1].rm_so); + + if ((error = disambiguate_refname(base_ref, repo, git_buf_cstr(&buf))) == 0) + goto cleanup; + + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; - git_buf_free(&datebuf); + error = maybe_sha_or_abbrev(out, repo, git_buf_cstr(&buf)); + + goto cleanup; } + + error = GIT_ENOTFOUND; - if (reflog) git_reflog_free(reflog); +cleanup: + git_reference_free(ref); git_buf_free(&buf); - return retcode; + regfree(&preg); + git_reflog_free(reflog); + return error; } -static git_object* dereference_object(git_object *obj) +static int retrieve_oid_from_reflog(git_oid *oid, git_reference *ref, unsigned int identifier) { - git_otype type = git_object_type(obj); + git_reflog *reflog; + int error = -1; + unsigned int numentries; + const git_reflog_entry *entry; + bool search_by_pos = (identifier <= 100000000); - switch (type) { - case GIT_OBJ_COMMIT: - { - git_tree *tree = NULL; - if (0 == git_commit_tree(&tree, (git_commit*)obj)) { - return (git_object*)tree; - } + if (git_reflog_read(&reflog, ref) < 0) + return -1; + + numentries = git_reflog_entrycount(reflog); + + if (search_by_pos) { + if (numentries < identifier + 1) { + giterr_set( + GITERR_REFERENCE, + "Reflog for '%s' has only %d entries, asked for %d", + git_reference_name(ref), + numentries, + identifier); + + error = GIT_ENOTFOUND; + goto cleanup; } - break; - case GIT_OBJ_TAG: - { - git_object *newobj = NULL; - if (0 == git_tag_target(&newobj, (git_tag*)obj)) { - return newobj; - } + + entry = git_reflog_entry_byindex(reflog, identifier); + git_oid_cpy(oid, git_reflog_entry_oidold(entry)); + error = 0; + goto cleanup; + + } else { + int i; + git_time commit_time; + + for (i = numentries - 1; i >= 0; i--) { + entry = git_reflog_entry_byindex(reflog, i); + commit_time = git_reflog_entry_committer(entry)->when; + + if (commit_time.time - identifier > 0) + continue; + + git_oid_cpy(oid, git_reflog_entry_oidnew(entry)); + error = 0; + goto cleanup; } - break; - - default: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - break; + + error = GIT_ENOTFOUND; } - /* Can't dereference some types */ - return NULL; +cleanup: + git_reflog_free(reflog); + return error; } -static int dereference_to_type(git_object **out, git_object *obj, git_otype target_type) +static int retrieve_revobject_from_reflog(git_object **out, git_reference **base_ref, git_repository *repo, const char *identifier, unsigned int position) { - int retcode = 1; - git_object *obj1 = obj, *obj2 = obj; - - while (retcode > 0) { - git_otype this_type = git_object_type(obj1); - - if (this_type == target_type) { - *out = obj1; - retcode = 0; - } else { - /* Dereference once, if possible. */ - obj2 = dereference_object(obj1); - if (!obj2) { - giterr_set(GITERR_REFERENCE, "Can't dereference to type"); - retcode = GIT_ERROR; - } - } - if (obj1 != obj && obj1 != obj2) { - git_object_free(obj1); - } - obj1 = obj2; + git_reference *ref; + git_oid oid; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; } - return retcode; + + if (position == 0) { + error = git_object_lookup(out, repo, git_reference_oid(ref), GIT_OBJ_ANY); + goto cleanup; + } + + if ((error = retrieve_oid_from_reflog(&oid, ref, position)) < 0) + goto cleanup; + + error = git_object_lookup(out, repo, &oid, GIT_OBJ_ANY); + +cleanup: + git_reference_free(ref); + return error; } -static git_otype parse_obj_type(const char *str) +static int retrieve_remote_tracking_reference(git_reference **base_ref, const char *identifier, git_repository *repo) { - if (!strcmp(str, "{commit}")) return GIT_OBJ_COMMIT; - if (!strcmp(str, "{tree}")) return GIT_OBJ_TREE; - if (!strcmp(str, "{blob}")) return GIT_OBJ_BLOB; - if (!strcmp(str, "{tag}")) return GIT_OBJ_TAG; - return GIT_OBJ_BAD; + git_reference *tracking, *ref; + int error = -1; + + if (*base_ref == NULL) { + if ((error = disambiguate_refname(&ref, repo, identifier)) < 0) + return error; + } else { + ref = *base_ref; + *base_ref = NULL; + } + + if ((error = git_branch_tracking(&tracking, ref)) < 0) + goto cleanup; + + *base_ref = tracking; + +cleanup: + git_reference_free(ref); + return error; } -static int handle_caret_syntax(git_object **out, git_repository *repo, git_object *obj, const char *movement) +static int handle_at_syntax(git_object **out, git_reference **ref, const char *spec, size_t identifier_len, git_repository* repo, const char *curly_braces_content) { - git_commit *commit; - size_t movementlen = strlen(movement); - int n; - - if (*movement == '{') { - if (movement[movementlen-1] != '}') { - set_invalid_syntax_err(movement); - return GIT_ERROR; - } + bool is_numeric; + int parsed = 0, error = -1; + git_buf identifier = GIT_BUF_INIT; + git_time_t timestamp; - /* {} -> Dereference until we reach an object that isn't a tag. */ - if (movementlen == 2) { - git_object *newobj = obj; - git_object *newobj2 = newobj; - while (git_object_type(newobj2) == GIT_OBJ_TAG) { - newobj2 = dereference_object(newobj); - if (newobj != obj) git_object_free(newobj); - if (!newobj2) { - giterr_set(GITERR_REFERENCE, "Couldn't find object of target type."); - return GIT_ERROR; - } - newobj = newobj2; - } - *out = newobj2; - return 0; - } + assert(*out == NULL); - /* {/...} -> Walk all commits until we see a commit msg that matches the phrase. */ - if (movement[1] == '/') { - int retcode = GIT_ERROR; - git_revwalk *walk; - if (!git_revwalk_new(&walk, repo)) { - git_oid oid; - regex_t preg; - int reg_error; - git_buf buf = GIT_BUF_INIT; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push(walk, git_object_id(obj)); - - /* Extract the regex from the movement string */ - git_buf_put(&buf, movement+2, strlen(movement)-3); - - reg_error = regcomp(&preg, git_buf_cstr(&buf), REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - while(!git_revwalk_next(&oid, walk)) { - git_object *walkobj; - - /* Fetch the commit object, and check for matches in the message */ - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Found it! */ - retcode = 0; - *out = walkobj; - if (obj == walkobj) { - /* Avoid leaking an object */ - git_object_free(walkobj); - } - break; - } - git_object_free(walkobj); - } - } - if (retcode < 0) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", movement); - } - regfree(&preg); - } + if (git_buf_put(&identifier, spec, identifier_len) < 0) + return -1; - git_buf_free(&buf); - git_revwalk_free(walk); - } - return retcode; - } + is_numeric = !try_parse_numeric(&parsed, curly_braces_content); - /* {...} -> Dereference until we reach an object of a certain type. */ - if (dereference_to_type(out, obj, parse_obj_type(movement)) < 0) { - return GIT_ERROR; - } - return 0; + if (*curly_braces_content == '-' && (!is_numeric || parsed == 0)) { + error = revspec_error(spec); + goto cleanup; } - /* Dereference until we reach a commit. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; - } + if (is_numeric) { + if (parsed < 0) + error = retrieve_previously_checked_out_branch_or_revision(out, ref, repo, spec, git_buf_cstr(&identifier), -parsed); + else + error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), parsed); - /* "^" is the same as "^1" */ - if (movementlen == 0) { - n = 1; - } else { - git__strtol32(&n, movement, NULL, 0); + goto cleanup; } - commit = (git_commit*)obj; - /* "^0" just returns the input */ - if (n == 0) { - *out = obj; - return 0; - } + if (!strcmp(curly_braces_content, "u") || !strcmp(curly_braces_content, "upstream")) { + error = retrieve_remote_tracking_reference(ref, git_buf_cstr(&identifier), repo); - if (git_commit_parent(&commit, commit, n-1) < 0) { - return GIT_ENOTFOUND; + goto cleanup; } - *out = (git_object*)commit; - return 0; + if (git__date_parse(×tamp, curly_braces_content) < 0) + goto cleanup; + + error = retrieve_revobject_from_reflog(out, ref, repo, git_buf_cstr(&identifier), (unsigned int)timestamp); + +cleanup: + git_buf_free(&identifier); + return error; } -static int handle_linear_syntax(git_object **out, git_object *obj, const char *movement) +static git_otype parse_obj_type(const char *str) { - git_commit *commit1, *commit2; - int i, n; + if (!strcmp(str, "commit")) + return GIT_OBJ_COMMIT; - /* Dereference until we reach a commit. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_COMMIT) < 0) { - /* Can't dereference to a commit; fail */ - return GIT_ERROR; - } + if (!strcmp(str, "tree")) + return GIT_OBJ_TREE; + + if (!strcmp(str, "blob")) + return GIT_OBJ_BLOB; - /* "~" is the same as "~1" */ - if (*movement == '\0') { - n = 1; - } else if (git__strtol32(&n, movement, NULL, 0) < 0) { - return GIT_ERROR; - } - commit1 = (git_commit*)obj; + if (!strcmp(str, "tag")) + return GIT_OBJ_TAG; + + return GIT_OBJ_BAD; +} + +static int dereference_to_non_tag(git_object **out, git_object *obj) +{ + if (git_object_type(obj) == GIT_OBJ_TAG) + return git_tag_peel(out, (git_tag *)obj); + + return git_object__dup(out, obj); +} + +static int handle_caret_parent_syntax(git_object **out, git_object *obj, int n) +{ + git_object *temp_commit = NULL; + int error; + + if (git_object_peel(&temp_commit, obj, GIT_OBJ_COMMIT) < 0) + return -1; - /* "~0" just returns the input */ if (n == 0) { - *out = obj; + *out = temp_commit; return 0; } - for (i=0; ioid) < 0) { - git__free(alloc); - return GIT_ERROR; - } - break; - case GIT_OBJ_BLOB: - if (*str != '\0') { - entry = NULL; - goto out; - } - break; - default: - /* TODO: support submodules? */ - giterr_set(GITERR_INVALID, "Unimplemented"); - git__free(alloc); - return GIT_ERROR; +static int walk_and_search(git_object **out, git_revwalk *walk, regex_t *regex) +{ + int error; + git_oid oid; + git_object *obj; + + while (!(error = git_revwalk_next(&oid, walk))) { + + if ((error = git_object_lookup(&obj, git_revwalk_repository(walk), &oid, GIT_OBJ_COMMIT) < 0) && + (error != GIT_ENOTFOUND)) + return -1; + + if (!regexec(regex, git_commit_message((git_commit*)obj), 0, NULL, 0)) { + *out = obj; + return 0; } + + git_object_free(obj); } -out: - if (!entry) { - giterr_set(GITERR_INVALID, "Invalid tree path '%s'", path); - git__free(alloc); - return GIT_ENOTFOUND; + if (error < 0 && error == GIT_ITEROVER) + error = GIT_ENOTFOUND; + + return error; +} + +static int handle_grep_syntax(git_object **out, git_repository *repo, const git_oid *spec_oid, const char *pattern) +{ + regex_t preg; + git_revwalk *walk = NULL; + int error = -1; + + if (build_regex(&preg, pattern) < 0) + return -1; + + if (git_revwalk_new(&walk, repo) < 0) + goto cleanup; + + git_revwalk_sorting(walk, GIT_SORT_TIME); + + if (spec_oid == NULL) { + // TODO: @carlosmn: The glob should be refs/* but this makes git_revwalk_next() fails + if (git_revwalk_push_glob(walk, "refs/heads/*") < 0) + goto cleanup; + } else if (git_revwalk_push(walk, spec_oid) < 0) + goto cleanup; + + error = walk_and_search(out, walk, &preg); + +cleanup: + regfree(&preg); + git_revwalk_free(walk); + + return error; +} + +static int handle_caret_curly_syntax(git_object **out, git_object *obj, const char *curly_braces_content) +{ + git_otype expected_type; + + if (*curly_braces_content == '\0') + return dereference_to_non_tag(out, obj); + + if (*curly_braces_content == '/') + return handle_grep_syntax(out, git_object_owner(obj), git_object_id(obj), curly_braces_content + 1); + + expected_type = parse_obj_type(curly_braces_content); + + if (expected_type == GIT_OBJ_BAD) + return -1; + + return git_object_peel(out, obj, expected_type); +} + +static int extract_curly_braces_content(git_buf *buf, const char *spec, size_t *pos) +{ + git_buf_clear(buf); + + assert(spec[*pos] == '^' || spec[*pos] == '@'); + + (*pos)++; + + if (spec[*pos] == '\0' || spec[*pos] != '{') + return revspec_error(spec); + + (*pos)++; + + while (spec[*pos] != '}') { + if (spec[*pos] == '\0') + return revspec_error(spec); + + git_buf_putc(buf, spec[(*pos)++]); } - git_oid_cpy(out, git_tree_entry_id(entry)); - git__free(alloc); + (*pos)++; + return 0; } -static int handle_colon_syntax(git_object **out, - git_repository *repo, - git_object *obj, - const char *path) +static int extract_path(git_buf *buf, const char *spec, size_t *pos) { - git_tree *tree; - git_oid oid; - int error; + git_buf_clear(buf); - /* Dereference until we reach a tree. */ - if (dereference_to_type(&obj, obj, GIT_OBJ_TREE) < 0) { - return GIT_ERROR; - } - tree = (git_tree*)obj; + assert(spec[*pos] == ':'); - /* Find the blob or tree at the given path. */ - error = oid_for_tree_path(&oid, tree, repo, path); - git_tree_free(tree); + (*pos)++; - if (error < 0) - return error; + if (git_buf_puts(buf, spec + *pos) < 0) + return -1; + + *pos += git_buf_len(buf); - return git_object_lookup(out, repo, &oid, GIT_OBJ_ANY); + return 0; } -static int revparse_global_grep(git_object **out, git_repository *repo, const char *pattern) +static int extract_how_many(int *n, const char *spec, size_t *pos) { - git_revwalk *walk; - int retcode = GIT_ERROR; + const char *end_ptr; + int parsed, accumulated; + char kind = spec[*pos]; - if (!pattern[0]) { - giterr_set(GITERR_REGEX, "Empty pattern"); - return GIT_ERROR; - } + assert(spec[*pos] == '^' || spec[*pos] == '~'); - if (!git_revwalk_new(&walk, repo)) { - regex_t preg; - int reg_error; - git_oid oid; - - git_revwalk_sorting(walk, GIT_SORT_TIME); - git_revwalk_push_glob(walk, "refs/heads/*"); - - reg_error = regcomp(&preg, pattern, REG_EXTENDED); - if (reg_error != 0) { - giterr_set_regex(&preg, reg_error); - } else { - git_object *walkobj = NULL, *resultobj = NULL; - while(!git_revwalk_next(&oid, walk)) { - /* Fetch the commit object, and check for matches in the message */ - if (walkobj != resultobj) git_object_free(walkobj); - if (!git_object_lookup(&walkobj, repo, &oid, GIT_OBJ_COMMIT)) { - if (!regexec(&preg, git_commit_message((git_commit*)walkobj), 0, NULL, 0)) { - /* Match! */ - resultobj = walkobj; - retcode = 0; - break; - } - } - } - if (!resultobj) { - giterr_set(GITERR_REFERENCE, "Couldn't find a match for %s", pattern); - retcode = GIT_ENOTFOUND; - git_object_free(walkobj); - } else { - *out = resultobj; - } - regfree(&preg); - git_revwalk_free(walk); + accumulated = 0; + + do { + do { + (*pos)++; + accumulated++; + } while (spec[(*pos)] == kind && kind == '~'); + + if (git__isdigit(spec[*pos])) { + if ((git__strtol32(&parsed, spec + *pos, &end_ptr, 10) < 0) < 0) + return revspec_error(spec); + + accumulated += (parsed - 1); + *pos = end_ptr - spec; } + + } while (spec[(*pos)] == kind && kind == '~'); + + *n = accumulated; + + return 0; +} + +static int object_from_reference(git_object **object, git_reference *reference) +{ + git_reference *resolved = NULL; + int error; + + if (git_reference_resolve(&resolved, reference) < 0) + return -1; + + error = git_object_lookup(object, reference->owner, git_reference_oid(resolved), GIT_OBJ_ANY); + git_reference_free(resolved); + + return error; +} + +static int ensure_base_rev_loaded(git_object **object, git_reference **reference, const char *spec, size_t identifier_len, git_repository *repo, bool allow_empty_identifier) +{ + int error; + git_buf identifier = GIT_BUF_INIT; + + if (*object != NULL) + return 0; + + if (*reference != NULL) { + if ((error = object_from_reference(object, *reference)) < 0) + return error; + + git_reference_free(*reference); + *reference = NULL; + return 0; } - return retcode; + if (!allow_empty_identifier && identifier_len == 0) + return revspec_error(spec); + + if (git_buf_put(&identifier, spec, identifier_len) < 0) + return -1; + + error = revparse_lookup_object(object, repo, git_buf_cstr(&identifier)); + git_buf_free(&identifier); + + return error; +} + +static int ensure_base_rev_is_not_known_yet(git_object *object, const char *spec) +{ + if (object == NULL) + return 0; + + return revspec_error(spec); +} + +static bool any_left_hand_identifier(git_object *object, git_reference *reference, size_t identifier_len) +{ + if (object != NULL) + return true; + + if (reference != NULL) + return true; + + if (identifier_len > 0) + return true; + + return false; +} + +static int ensure_left_hand_identifier_is_not_known_yet(git_object *object, git_reference *reference, const char *spec) +{ + if (!ensure_base_rev_is_not_known_yet(object, spec) && reference == NULL) + return 0; + + return revspec_error(spec); } int git_revparse_single(git_object **out, git_repository *repo, const char *spec) { - revparse_state current_state = REVPARSE_STATE_INIT, next_state = REVPARSE_STATE_INIT; - const char *spec_cur = spec; - git_object *cur_obj = NULL, *next_obj = NULL; - git_buf specbuffer = GIT_BUF_INIT, stepbuffer = GIT_BUF_INIT; - int retcode = 0; + size_t pos = 0, identifier_len = 0; + int error = -1, n; + git_buf buf = GIT_BUF_INIT; + + git_reference *reference = NULL; + git_object *base_rev = NULL; assert(out && repo && spec); - if (spec[0] == ':') { - if (spec[1] == '/') { - return revparse_global_grep(out, repo, spec+2); - } - /* TODO: support merge-stage path lookup (":2:Makefile"). */ - giterr_set(GITERR_INVALID, "Unimplemented"); - return GIT_ERROR; - } + *out = NULL; - while (current_state != REVPARSE_STATE_DONE) { - switch (current_state) { - case REVPARSE_STATE_INIT: - if (!*spec_cur) { - /* No operators, just a name. Find it and return. */ - retcode = revparse_lookup_object(out, repo, spec); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '@') { - /* '@' syntax doesn't allow chaining */ - git_buf_puts(&stepbuffer, spec_cur); - retcode = walk_ref_history(out, repo, git_buf_cstr(&specbuffer), git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - next_state = REVPARSE_STATE_CARET; - } else if (*spec_cur == '~') { - next_state = REVPARSE_STATE_LINEAR; - } else if (*spec_cur == ':') { - next_state = REVPARSE_STATE_COLON; - } else { - git_buf_putc(&specbuffer, *spec_cur); - } - spec_cur++; + while (spec[pos]) { + switch (spec[pos]) { + case '^': + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; - if (current_state != next_state && next_state != REVPARSE_STATE_DONE) { - /* Leaving INIT state, find the object specified, in case that state needs it */ - if (revparse_lookup_object(&next_obj, repo, git_buf_cstr(&specbuffer)) < 0) { - retcode = GIT_ERROR; - next_state = REVPARSE_STATE_DONE; - } - } - break; + if (spec[pos+1] == '{') { + git_object *temp_object = NULL; + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; - case REVPARSE_STATE_CARET: - /* Gather characters until NULL, '~', or '^' */ - if (!*spec_cur) { - retcode = handle_caret_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_LINEAR : REVPARSE_STATE_DONE; - } else if (*spec_cur == '^') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; - } - } else if (*spec_cur == ':') { - retcode = handle_caret_syntax(&next_obj, repo, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_COLON : REVPARSE_STATE_DONE; + if ((error = handle_caret_curly_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; } else { - git_buf_putc(&stepbuffer, *spec_cur); + git_object *temp_object = NULL; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = handle_caret_parent_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; } - spec_cur++; break; - case REVPARSE_STATE_LINEAR: - if (!*spec_cur) { - retcode = handle_linear_syntax(out, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; - } else if (*spec_cur == '~') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - if (retcode < 0) { - next_state = REVPARSE_STATE_DONE; - } - } else if (*spec_cur == '^') { - retcode = handle_linear_syntax(&next_obj, cur_obj, git_buf_cstr(&stepbuffer)); - git_buf_clear(&stepbuffer); - next_state = !retcode ? REVPARSE_STATE_CARET : REVPARSE_STATE_DONE; - } else { - git_buf_putc(&stepbuffer, *spec_cur); - } - spec_cur++; + case '~': + { + git_object *temp_object = NULL; + + if ((error = extract_how_many(&n, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; + + if ((error = handle_linear_syntax(&temp_object, base_rev, n)) < 0) + goto cleanup; + + git_object_free(base_rev); + base_rev = temp_object; break; + } + + case ':': + { + git_object *temp_object = NULL; + + if ((error = extract_path(&buf, spec, &pos)) < 0) + goto cleanup; - case REVPARSE_STATE_COLON: - if (*spec_cur) { - git_buf_putc(&stepbuffer, *spec_cur); + if (any_left_hand_identifier(base_rev, reference, identifier_len)) { + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, true)) < 0) + goto cleanup; + + if ((error = handle_colon_syntax(&temp_object, base_rev, git_buf_cstr(&buf))) < 0) + goto cleanup; } else { - retcode = handle_colon_syntax(out, repo, cur_obj, git_buf_cstr(&stepbuffer)); - next_state = REVPARSE_STATE_DONE; + if (*git_buf_cstr(&buf) == '/') { + if ((error = handle_grep_syntax(&temp_object, repo, NULL, git_buf_cstr(&buf) + 1)) < 0) + goto cleanup; + } else { + + /* + * TODO: support merge-stage path lookup (":2:Makefile") + * and plain index blob lookup (:i-am/a/blob) + */ + giterr_set(GITERR_INVALID, "Unimplemented"); + error = GIT_ERROR; + goto cleanup; + } } - spec_cur++; + + git_object_free(base_rev); + base_rev = temp_object; break; + } + + case '@': + { + git_object *temp_object = NULL; + + if ((error = extract_curly_braces_content(&buf, spec, &pos)) < 0) + goto cleanup; + + if ((error = ensure_base_rev_is_not_known_yet(base_rev, spec)) < 0) + goto cleanup; - case REVPARSE_STATE_DONE: - if (cur_obj && *out != cur_obj) git_object_free(cur_obj); - if (next_obj && *out != next_obj) git_object_free(next_obj); + if ((error = handle_at_syntax(&temp_object, &reference, spec, identifier_len, repo, git_buf_cstr(&buf))) < 0) + goto cleanup; + + if (temp_object != NULL) + base_rev = temp_object; break; } - current_state = next_state; - if (cur_obj != next_obj) { - if (cur_obj) git_object_free(cur_obj); - cur_obj = next_obj; + default: + if ((error = ensure_left_hand_identifier_is_not_known_yet(base_rev, reference, spec)) < 0) + goto cleanup; + + pos++; + identifier_len++; } } - if (*out != cur_obj) git_object_free(cur_obj); - if (*out != next_obj && next_obj != cur_obj) git_object_free(next_obj); + if ((error = ensure_base_rev_loaded(&base_rev, &reference, spec, identifier_len, repo, false)) < 0) + goto cleanup; - git_buf_free(&specbuffer); - git_buf_free(&stepbuffer); - return retcode; + *out = base_rev; + error = 0; + +cleanup: + if (error) + git_object_free(base_rev); + git_reference_free(reference); + git_buf_free(&buf); + return error; } diff --git a/src/revwalk.c b/src/revwalk.c index 7bcdc4af8ee..8141d177b33 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -188,7 +188,7 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo const size_t parent_len = strlen("parent ") + GIT_OID_HEXSZ + 1; unsigned char *buffer = raw->data; unsigned char *buffer_end = buffer + raw->len; - unsigned char *parents_start; + unsigned char *parents_start, *committer_start; int i, parents = 0; int commit_time; @@ -219,17 +219,34 @@ static int commit_quick_parse(git_revwalk *walk, commit_object *commit, git_rawo commit->out_degree = (unsigned short)parents; + if ((committer_start = buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) + return commit_error(commit, "object is corrupted"); + + buffer++; + if ((buffer = memchr(buffer, '\n', buffer_end - buffer)) == NULL) return commit_error(commit, "object is corrupted"); - if ((buffer = memchr(buffer, '<', buffer_end - buffer)) == NULL || - (buffer = memchr(buffer, '>', buffer_end - buffer)) == NULL) - return commit_error(commit, "malformed author information"); + /* Skip trailing spaces */ + while (buffer > committer_start && git__isspace(*buffer)) + buffer--; + + /* Seek for the begining of the pack of digits */ + while (buffer > committer_start && git__isdigit(*buffer)) + buffer--; + + /* Skip potential timezone offset */ + if ((buffer > committer_start) && (*buffer == '+' || *buffer == '-')) { + buffer--; - while (*buffer == '>' || git__isspace(*buffer)) - buffer++; + while (buffer > committer_start && git__isspace(*buffer)) + buffer--; - if (git__strtol32(&commit_time, (char *)buffer, NULL, 10) < 0) + while (buffer > committer_start && git__isdigit(*buffer)) + buffer--; + } + + if ((buffer == committer_start) || (git__strtol32(&commit_time, (char *)(buffer + 1), NULL, 10) < 0)) return commit_error(commit, "cannot parse commit time"); commit->time = (time_t)commit_time; @@ -247,12 +264,7 @@ static int commit_parse(git_revwalk *walk, commit_object *commit) if ((error = git_odb_read(&obj, walk->odb, &commit->oid)) < 0) return error; - - if (obj->raw.type != GIT_OBJ_COMMIT) { - git_odb_object_free(obj); - giterr_set(GITERR_INVALID, "Failed to parse commit. Object is no commit object"); - return -1; - } + assert(obj->raw.type == GIT_OBJ_COMMIT); error = commit_quick_parse(walk, commit, &obj->raw); git_odb_object_free(obj); @@ -407,7 +419,7 @@ int git_merge_base_many(git_oid *out, git_repository *repo, const git_oid input_ return error; } -int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *two) +int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const git_oid *two) { git_revwalk *walk; git_vector list; @@ -437,6 +449,7 @@ int git_merge_base(git_oid *out, git_repository *repo, git_oid *one, git_oid *tw if (!result) { git_revwalk_free(walk); + giterr_clear(); return GIT_ENOTFOUND; } @@ -498,8 +511,21 @@ static int process_commit_parents(git_revwalk *walk, commit_object *commit) static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting) { + git_object *obj; + git_otype type; commit_object *commit; + if (git_object_lookup(&obj, walk->repo, oid, GIT_OBJ_ANY) < 0) + return -1; + + type = git_object_type(obj); + git_object_free(obj); + + if (type != GIT_OBJ_COMMIT) { + giterr_set(GITERR_INVALID, "Object is no commit object"); + return -1; + } + commit = commit_lookup(walk, oid); if (commit == NULL) return -1; /* error already reported by failed lookup */ @@ -657,7 +683,8 @@ static int revwalk_next_timesort(commit_object **object_out, git_revwalk *walk) } } - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) @@ -675,7 +702,8 @@ static int revwalk_next_unsorted(commit_object **object_out, git_revwalk *walk) } } - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) @@ -685,8 +713,10 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) for (;;) { next = commit_list_pop(&walk->iterator_topo); - if (next == NULL) - return GIT_REVWALKOVER; + if (next == NULL) { + giterr_clear(); + return GIT_ITEROVER; + } if (next->in_degree > 0) { next->topo_delay = 1; @@ -711,7 +741,7 @@ static int revwalk_next_toposort(commit_object **object_out, git_revwalk *walk) static int revwalk_next_reverse(commit_object **object_out, git_revwalk *walk) { *object_out = commit_list_pop(&walk->iterator_reverse); - return *object_out ? 0 : GIT_REVWALKOVER; + return *object_out ? 0 : GIT_ITEROVER; } @@ -726,8 +756,10 @@ static int prepare_walk(git_revwalk *walk) * If walk->one is NULL, there were no positive references, * so we know that the walk is already over. */ - if (walk->one == NULL) - return GIT_REVWALKOVER; + if (walk->one == NULL) { + giterr_clear(); + return GIT_ITEROVER; + } /* first figure out what the merge bases are */ if (merge_bases_many(&bases, walk, walk->one, &walk->twos) < 0) @@ -755,7 +787,7 @@ static int prepare_walk(git_revwalk *walk) return -1; } - if (error != GIT_REVWALKOVER) + if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_toposort; @@ -767,7 +799,7 @@ static int prepare_walk(git_revwalk *walk) if (commit_list_insert(next, &walk->iterator_reverse) == NULL) return -1; - if (error != GIT_REVWALKOVER) + if (error != GIT_ITEROVER) return error; walk->get_next = &revwalk_next_reverse; @@ -866,9 +898,10 @@ int git_revwalk_next(git_oid *oid, git_revwalk *walk) error = walk->get_next(&next, walk); - if (error == GIT_REVWALKOVER) { + if (error == GIT_ITEROVER) { git_revwalk_reset(walk); - return GIT_REVWALKOVER; + giterr_clear(); + return GIT_ITEROVER; } if (!error) diff --git a/src/sha1.h b/src/sha1.h index 93a244d769c..f0a16f2cfa0 100644 --- a/src/sha1.h +++ b/src/sha1.h @@ -5,6 +5,9 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#ifndef INCLUDE_sha1_h__ +#define INCLUDE_sha1_h__ + typedef struct { unsigned long long size; unsigned int H[5]; @@ -19,3 +22,5 @@ void git__blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); #define SHA1_Init git__blk_SHA1_Init #define SHA1_Update git__blk_SHA1_Update #define SHA1_Final git__blk_SHA1_Final + +#endif diff --git a/src/signature.c b/src/signature.c index 332bdf65f0b..0159488a4d7 100644 --- a/src/signature.c +++ b/src/signature.c @@ -40,7 +40,7 @@ static const char *skip_trailing_spaces(const char *buffer_start, const char *bu static int signature_error(const char *msg) { - giterr_set(GITERR_INVALID, "Failed to parse signature - %s", msg); + giterr_set(GITERR_INVALID, "Failed to process signature - %s", msg); return -1; } @@ -72,9 +72,16 @@ static int process_trimming(const char *input, char **storage, const char *input return 0; } +static bool contains_angle_brackets(const char *input) +{ + if (strchr(input, '<') != NULL) + return true; + + return strchr(input, '>') != NULL; +} + int git_signature_new(git_signature **sig_out, const char *name, const char *email, git_time_t time, int offset) { - int error; git_signature *p = NULL; assert(name && email); @@ -84,11 +91,18 @@ int git_signature_new(git_signature **sig_out, const char *name, const char *ema p = git__calloc(1, sizeof(git_signature)); GITERR_CHECK_ALLOC(p); - if ((error = process_trimming(name, &p->name, name + strlen(name), 1)) < 0 || - (error = process_trimming(email, &p->email, email + strlen(email), 1)) < 0) + if (process_trimming(name, &p->name, name + strlen(name), 1) < 0 || + process_trimming(email, &p->email, email + strlen(email), 1) < 0) { git_signature_free(p); - return error; + return -1; + } + + if (contains_angle_brackets(p->email) || + contains_angle_brackets(p->name)) + { + git_signature_free(p); + return signature_error("Neither `name` nor `email` should contain angle brackets chars."); } p->when.time = time; @@ -111,24 +125,26 @@ int git_signature_now(git_signature **sig_out, const char *name, const char *ema { time_t now; time_t offset; - struct tm *utc_tm, *local_tm; + struct tm *utc_tm; git_signature *sig; - struct tm _utc, _local; + struct tm _utc; *sig_out = NULL; + /* + * Get the current time as seconds since the epoch and + * transform that into a tm struct containing the time at + * UTC. Give that to mktime which considers it a local time + * (tm_isdst = -1 asks it to take DST into account) and gives + * us that time as seconds since the epoch. The difference + * between its return value and 'now' is our offset to UTC. + */ time(&now); - utc_tm = p_gmtime_r(&now, &_utc); - local_tm = p_localtime_r(&now, &_local); - - offset = mktime(local_tm) - mktime(utc_tm); + utc_tm->tm_isdst = -1; + offset = (time_t)difftime(now, mktime(utc_tm)); offset /= 60; - /* mktime takes care of setting tm_isdst correctly */ - if (local_tm->tm_isdst) - offset += 60; - if (git_signature_new(&sig, name, email, now, (int)offset) < 0) return -1; diff --git a/src/status.c b/src/status.c index e9ad3cfe4d3..0a5fbdcbf79 100644 --- a/src/status.c +++ b/src/status.c @@ -81,7 +81,7 @@ int git_status_foreach_ext( git_status_show_t show = opts ? opts->show : GIT_STATUS_SHOW_INDEX_AND_WORKDIR; git_diff_delta *i2h, *w2i; - unsigned int i, j, i_max, j_max; + size_t i, j, i_max, j_max; assert(show <= GIT_STATUS_SHOW_INDEX_THEN_WORKDIR); @@ -99,6 +99,8 @@ int git_status_foreach_ext( diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED; if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0) diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0) + diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH; /* TODO: support EXCLUDE_SUBMODULES flag */ if (show != GIT_STATUS_SHOW_WORKDIR_ONLY && @@ -112,7 +114,8 @@ int git_status_foreach_ext( if (show == GIT_STATUS_SHOW_INDEX_THEN_WORKDIR) { for (i = 0; !err && i < idx2head->deltas.length; i++) { i2h = GIT_VECTOR_GET(&idx2head->deltas, i); - err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata)) + err = GIT_EUSER; } git_diff_list_free(idx2head); idx2head = NULL; @@ -128,14 +131,17 @@ int git_status_foreach_ext( cmp = !w2i ? -1 : !i2h ? 1 : strcmp(i2h->old_file.path, w2i->old_file.path); if (cmp < 0) { - err = cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata); + if (cb(i2h->old_file.path, index_delta2status(i2h->status), cbdata)) + err = GIT_EUSER; i++; } else if (cmp > 0) { - err = cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata); + if (cb(w2i->old_file.path, workdir_delta2status(w2i->status), cbdata)) + err = GIT_EUSER; j++; } else { - err = cb(i2h->old_file.path, index_delta2status(i2h->status) | - workdir_delta2status(w2i->status), cbdata); + if (cb(i2h->old_file.path, index_delta2status(i2h->status) | + workdir_delta2status(w2i->status), cbdata)) + err = GIT_EUSER; i++; j++; } } @@ -144,6 +150,10 @@ int git_status_foreach_ext( git_tree_free(head); git_diff_list_free(idx2head); git_diff_list_free(wd2idx); + + if (err == GIT_EUSER) + giterr_clear(); + return err; } @@ -164,9 +174,10 @@ int git_status_foreach( } struct status_file_info { + char *expected; unsigned int count; unsigned int status; - char *expected; + int ambiguous; }; static int get_one_status(const char *path, unsigned int status, void *data) @@ -176,10 +187,13 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; sfi->status = status; - if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) { + if (sfi->count > 1 || + (strcmp(sfi->expected, path) != 0 && + p_fnmatch(sfi->expected, path, 0) != 0)) { giterr_set(GITERR_INVALID, "Ambiguous path '%s' given to git_status_file", sfi->expected); - return -1; + sfi->ambiguous = true; + return GIT_EAMBIGUOUS; } return 0; @@ -211,6 +225,9 @@ int git_status_file( error = git_status_foreach_ext(repo, &opts, get_one_status, &sfi); + if (error < 0 && sfi.ambiguous) + error = GIT_EAMBIGUOUS; + if (!error && !sfi.count) { giterr_set(GITERR_INVALID, "Attempt to get status of nonexistent file '%s'", path); @@ -229,14 +246,6 @@ int git_status_should_ignore( git_repository *repo, const char *path) { - int error; - git_ignores ignores; - - if (git_ignore__for_path(repo, path, &ignores) < 0) - return -1; - - error = git_ignore__lookup(&ignores, path, ignored); - git_ignore__free(&ignores); - return error; + return git_ignore_path_is_ignored(ignored, repo, path); } diff --git a/src/strmap.h b/src/strmap.h index da5ca0dbac8..9972039a0b1 100644 --- a/src/strmap.h +++ b/src/strmap.h @@ -19,7 +19,7 @@ __KHASH_TYPE(str, const char *, void *); typedef khash_t(str) git_strmap; #define GIT__USE_STRMAP \ - __KHASH_IMPL(str, static inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) + __KHASH_IMPL(str, static kh_inline, const char *, void *, 1, kh_str_hash_func, kh_str_hash_equal) #define git_strmap_alloc() kh_init(str) #define git_strmap_free(h) kh_destroy(str, h), h = NULL diff --git a/src/submodule.c b/src/submodule.c index 3c07e657d10..5ae38bccd99 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -17,59 +17,847 @@ #include "config_file.h" #include "config.h" #include "repository.h" +#include "submodule.h" +#include "tree.h" +#include "iterator.h" + +#define GIT_MODULES_FILE ".gitmodules" static git_cvar_map _sm_update_map[] = { {GIT_CVAR_STRING, "checkout", GIT_SUBMODULE_UPDATE_CHECKOUT}, {GIT_CVAR_STRING, "rebase", GIT_SUBMODULE_UPDATE_REBASE}, - {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE} + {GIT_CVAR_STRING, "merge", GIT_SUBMODULE_UPDATE_MERGE}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_UPDATE_NONE}, }; static git_cvar_map _sm_ignore_map[] = { - {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, - {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE}, {GIT_CVAR_STRING, "untracked", GIT_SUBMODULE_IGNORE_UNTRACKED}, - {GIT_CVAR_STRING, "none", GIT_SUBMODULE_IGNORE_NONE} + {GIT_CVAR_STRING, "dirty", GIT_SUBMODULE_IGNORE_DIRTY}, + {GIT_CVAR_STRING, "all", GIT_SUBMODULE_IGNORE_ALL}, }; -static inline khint_t str_hash_no_trailing_slash(const char *s) +static kh_inline khint_t str_hash_no_trailing_slash(const char *s) { khint_t h; for (h = 0; *s; ++s) - if (s[1] || *s != '/') + if (s[1] != '\0' || *s != '/') h = (h << 5) - h + *s; return h; } -static inline int str_equal_no_trailing_slash(const char *a, const char *b) +static kh_inline int str_equal_no_trailing_slash(const char *a, const char *b) { size_t alen = a ? strlen(a) : 0; size_t blen = b ? strlen(b) : 0; - if (alen && a[alen] == '/') + if (alen > 0 && a[alen - 1] == '/') alen--; - if (blen && b[blen] == '/') + if (blen > 0 && b[blen - 1] == '/') blen--; return (alen == blen && strncmp(a, b, alen) == 0); } -__KHASH_IMPL(str, static inline, const char *, void *, 1, str_hash_no_trailing_slash, str_equal_no_trailing_slash); +__KHASH_IMPL( + str, static kh_inline, const char *, void *, 1, + str_hash_no_trailing_slash, str_equal_no_trailing_slash); + +static int load_submodule_config(git_repository *repo, bool force); +static git_config_file *open_gitmodules(git_repository *, bool, const git_oid *); +static int lookup_head_remote(git_buf *url, git_repository *repo); +static int submodule_get(git_submodule **, git_repository *, const char *, const char *); +static void submodule_release(git_submodule *sm, int decr); +static int submodule_load_from_index(git_repository *, const git_index_entry *); +static int submodule_load_from_head(git_repository*, const char*, const git_oid*); +static int submodule_load_from_config(const char *, const char *, void *); +static int submodule_load_from_wd_lite(git_submodule *, const char *, void *); +static int submodule_update_config(git_submodule *, const char *, const char *, bool, bool); +static void submodule_mode_mismatch(git_repository *, const char *, unsigned int); +static int submodule_index_status(unsigned int *status, git_submodule *sm); +static int submodule_wd_status(unsigned int *status, git_submodule *sm); -static git_submodule *submodule_alloc(const char *name) +static int submodule_cmp(const void *a, const void *b) { - git_submodule *sm = git__calloc(1, sizeof(git_submodule)); - if (sm == NULL) - return sm; + return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); +} - sm->path = sm->name = git__strdup(name); - if (!sm->name) { - git__free(sm); +static int submodule_config_key_trunc_puts(git_buf *key, const char *suffix) +{ + ssize_t idx = git_buf_rfind(key, '.'); + git_buf_truncate(key, (size_t)(idx + 1)); + return git_buf_puts(key, suffix); +} + +/* + * PUBLIC APIS + */ + +int git_submodule_lookup( + git_submodule **sm_ptr, /* NULL if user only wants to test existence */ + git_repository *repo, + const char *name) /* trailing slash is allowed */ +{ + int error; + khiter_t pos; + + assert(repo && name); + + if ((error = load_submodule_config(repo, false)) < 0) + return error; + + pos = git_strmap_lookup_index(repo->submodules, name); + + if (!git_strmap_valid_index(repo->submodules, pos)) { + error = GIT_ENOTFOUND; + + /* check if a plausible submodule exists at path */ + if (git_repository_workdir(repo)) { + git_buf path = GIT_BUF_INIT; + + if (git_buf_joinpath(&path, git_repository_workdir(repo), name) < 0) + return -1; + + if (git_path_contains_dir(&path, DOT_GIT)) + error = GIT_EEXISTS; + + git_buf_free(&path); + } + + return error; + } + + if (sm_ptr) + *sm_ptr = git_strmap_value_at(repo->submodules, pos); + + return 0; +} + +int git_submodule_foreach( + git_repository *repo, + int (*callback)(git_submodule *sm, const char *name, void *payload), + void *payload) +{ + int error; + git_submodule *sm; + git_vector seen = GIT_VECTOR_INIT; + seen._cmp = submodule_cmp; + + assert(repo && callback); + + if ((error = load_submodule_config(repo, false)) < 0) + return error; + + git_strmap_foreach_value(repo->submodules, sm, { + /* Usually the following will not come into play - it just prevents + * us from issuing a callback twice for a submodule where the name + * and path are not the same. + */ + if (sm->refcount > 1) { + if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) + continue; + if ((error = git_vector_insert(&seen, sm)) < 0) + break; + } + + if (callback(sm, sm->name, payload)) { + giterr_clear(); + error = GIT_EUSER; + break; + } + }); + + git_vector_free(&seen); + + return error; +} + +void git_submodule_config_free(git_repository *repo) +{ + git_strmap *smcfg; + git_submodule *sm; + + assert(repo); + + smcfg = repo->submodules; + repo->submodules = NULL; + + if (smcfg == NULL) + return; + + git_strmap_foreach_value(smcfg, sm, { + submodule_release(sm,1); + }); + git_strmap_free(smcfg); +} + +int git_submodule_add_setup( + git_submodule **submodule, + git_repository *repo, + const char *url, + const char *path, + int use_gitlink) +{ + int error = 0; + git_config_file *mods = NULL; + git_submodule *sm; + git_buf name = GIT_BUF_INIT, real_url = GIT_BUF_INIT; + git_repository_init_options initopt; + git_repository *subrepo = NULL; + + assert(repo && url && path); + + /* see if there is already an entry for this submodule */ + + if (git_submodule_lookup(&sm, repo, path) < 0) + giterr_clear(); + else { + giterr_set(GITERR_SUBMODULE, + "Attempt to add a submodule that already exists"); + return GIT_EEXISTS; + } + + /* resolve parameters */ + + if (url[0] == '.' && (url[1] == '/' || (url[1] == '.' && url[2] == '/'))) { + if (!(error = lookup_head_remote(&real_url, repo))) + error = git_path_apply_relative(&real_url, url); + } else if (strchr(url, ':') != NULL || url[0] == '/') { + error = git_buf_sets(&real_url, url); + } else { + giterr_set(GITERR_SUBMODULE, "Invalid format for submodule URL"); + error = -1; + } + if (error) + goto cleanup; + + /* validate and normalize path */ + + if (git__prefixcmp(path, git_repository_workdir(repo)) == 0) + path += strlen(git_repository_workdir(repo)); + + if (git_path_root(path) >= 0) { + giterr_set(GITERR_SUBMODULE, "Submodule path must be a relative path"); + error = -1; + goto cleanup; + } + + /* update .gitmodules */ + + if ((mods = open_gitmodules(repo, true, NULL)) == NULL) { + giterr_set(GITERR_SUBMODULE, + "Adding submodules to a bare repository is not supported (for now)"); + return -1; + } + + if ((error = git_buf_printf(&name, "submodule.%s.path", path)) < 0 || + (error = git_config_file_set_string(mods, name.ptr, path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&name, "url")) < 0 || + (error = git_config_file_set_string(mods, name.ptr, real_url.ptr)) < 0) + goto cleanup; + + git_buf_clear(&name); + + /* init submodule repository and add origin remote as needed */ + + error = git_buf_joinpath(&name, git_repository_workdir(repo), path); + if (error < 0) + goto cleanup; + + /* New style: sub-repo goes in /modules// with a + * gitlink in the sub-repo workdir directory to that repository + * + * Old style: sub-repo goes directly into repo//.git/ + */ + + memset(&initopt, 0, sizeof(initopt)); + initopt.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_REINIT; + initopt.origin_url = real_url.ptr; + + if (git_path_exists(name.ptr) && + git_path_contains(&name, DOT_GIT)) + { + /* repo appears to already exist - reinit? */ + } + else if (use_gitlink) { + git_buf repodir = GIT_BUF_INIT; + + error = git_buf_join_n( + &repodir, '/', 3, git_repository_path(repo), "modules", path); + if (error < 0) + goto cleanup; + + initopt.workdir_path = name.ptr; + initopt.flags |= GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + + error = git_repository_init_ext(&subrepo, repodir.ptr, &initopt); + + git_buf_free(&repodir); + } + else { + error = git_repository_init_ext(&subrepo, name.ptr, &initopt); + } + if (error < 0) + goto cleanup; + + /* add submodule to hash and "reload" it */ + + if (!(error = submodule_get(&sm, repo, path, NULL)) && + !(error = git_submodule_reload(sm))) + error = git_submodule_init(sm, false); + +cleanup: + if (submodule != NULL) + *submodule = !error ? sm : NULL; + + if (mods != NULL) + git_config_file_free(mods); + git_repository_free(subrepo); + git_buf_free(&real_url); + git_buf_free(&name); + + return error; +} + +int git_submodule_add_finalize(git_submodule *sm) +{ + int error; + git_index *index; + + assert(sm); + + if ((error = git_repository_index__weakptr(&index, sm->owner)) < 0 || + (error = git_index_add(index, GIT_MODULES_FILE, 0)) < 0) + return error; + + return git_submodule_add_to_index(sm, true); +} + +int git_submodule_add_to_index(git_submodule *sm, int write_index) +{ + int error; + git_repository *repo, *sm_repo; + git_index *index; + git_buf path = GIT_BUF_INIT; + git_commit *head; + git_index_entry entry; + struct stat st; + + assert(sm); + + repo = sm->owner; + + /* force reload of wd OID by git_submodule_open */ + sm->flags = sm->flags & ~GIT_SUBMODULE_STATUS__WD_OID_VALID; + + if ((error = git_repository_index__weakptr(&index, repo)) < 0 || + (error = git_buf_joinpath( + &path, git_repository_workdir(repo), sm->path)) < 0 || + (error = git_submodule_open(&sm_repo, sm)) < 0) + goto cleanup; + + /* read stat information for submodule working directory */ + if (p_stat(path.ptr, &st) < 0) { + giterr_set(GITERR_SUBMODULE, + "Cannot add submodule without working directory"); + error = -1; + goto cleanup; + } + + memset(&entry, 0, sizeof(entry)); + entry.path = sm->path; + git_index__init_entry_from_stat(&st, &entry); + + /* calling git_submodule_open will have set sm->wd_oid if possible */ + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) == 0) { + giterr_set(GITERR_SUBMODULE, + "Cannot add submodule without HEAD to index"); + error = -1; + goto cleanup; + } + git_oid_cpy(&entry.oid, &sm->wd_oid); + + if ((error = git_commit_lookup(&head, sm_repo, &sm->wd_oid)) < 0) + goto cleanup; + + entry.ctime.seconds = git_commit_time(head); + entry.ctime.nanoseconds = 0; + entry.mtime.seconds = git_commit_time(head); + entry.mtime.nanoseconds = 0; + + git_commit_free(head); + + /* add it */ + error = git_index_add2(index, &entry); + + /* write it, if requested */ + if (!error && write_index) { + error = git_index_write(index); + + if (!error) + git_oid_cpy(&sm->index_oid, &sm->wd_oid); + } + +cleanup: + git_repository_free(sm_repo); + git_buf_free(&path); + return error; +} + +int git_submodule_save(git_submodule *submodule) +{ + int error = 0; + git_config_file *mods; + git_buf key = GIT_BUF_INIT; + + assert(submodule); + + mods = open_gitmodules(submodule->owner, true, NULL); + if (!mods) { + giterr_set(GITERR_SUBMODULE, + "Adding submodules to a bare repository is not supported (for now)"); + return -1; + } + + if ((error = git_buf_printf(&key, "submodule.%s.", submodule->name)) < 0) + goto cleanup; + + /* save values for path, url, update, ignore, fetchRecurseSubmodules */ + + if ((error = submodule_config_key_trunc_puts(&key, "path")) < 0 || + (error = git_config_file_set_string(mods, key.ptr, submodule->path)) < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts(&key, "url")) < 0 || + (error = git_config_file_set_string(mods, key.ptr, submodule->url)) < 0) + goto cleanup; + + if (!(error = submodule_config_key_trunc_puts(&key, "update")) && + submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) + { + const char *val = (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) ? + NULL : _sm_update_map[submodule->update].str_match; + error = git_config_file_set_string(mods, key.ptr, val); + } + if (error < 0) + goto cleanup; + + if (!(error = submodule_config_key_trunc_puts(&key, "ignore")) && + submodule->ignore != GIT_SUBMODULE_IGNORE_DEFAULT) + { + const char *val = (submodule->ignore == GIT_SUBMODULE_IGNORE_NONE) ? + NULL : _sm_ignore_map[submodule->ignore].str_match; + error = git_config_file_set_string(mods, key.ptr, val); + } + if (error < 0) + goto cleanup; + + if ((error = submodule_config_key_trunc_puts( + &key, "fetchRecurseSubmodules")) < 0 || + (error = git_config_file_set_string( + mods, key.ptr, submodule->fetch_recurse ? "true" : "false")) < 0) + goto cleanup; + + /* update internal defaults */ + + submodule->ignore_default = submodule->ignore; + submodule->update_default = submodule->update; + submodule->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + +cleanup: + if (mods != NULL) + git_config_file_free(mods); + git_buf_free(&key); + + return error; +} + +git_repository *git_submodule_owner(git_submodule *submodule) +{ + assert(submodule); + return submodule->owner; +} + +const char *git_submodule_name(git_submodule *submodule) +{ + assert(submodule); + return submodule->name; +} + +const char *git_submodule_path(git_submodule *submodule) +{ + assert(submodule); + return submodule->path; +} + +const char *git_submodule_url(git_submodule *submodule) +{ + assert(submodule); + return submodule->url; +} + +int git_submodule_set_url(git_submodule *submodule, const char *url) +{ + assert(submodule && url); + + git__free(submodule->url); + + submodule->url = git__strdup(url); + GITERR_CHECK_ALLOC(submodule->url); + + return 0; +} + +const git_oid *git_submodule_index_oid(git_submodule *submodule) +{ + assert(submodule); + + if (submodule->flags & GIT_SUBMODULE_STATUS__INDEX_OID_VALID) + return &submodule->index_oid; + else + return NULL; +} + +const git_oid *git_submodule_head_oid(git_submodule *submodule) +{ + assert(submodule); + + if (submodule->flags & GIT_SUBMODULE_STATUS__HEAD_OID_VALID) + return &submodule->head_oid; + else return NULL; +} + +const git_oid *git_submodule_wd_oid(git_submodule *submodule) +{ + assert(submodule); + + if (!(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + git_repository *subrepo; + + /* calling submodule open grabs the HEAD OID if possible */ + if (!git_submodule_open(&subrepo, submodule)) + git_repository_free(subrepo); + else + giterr_clear(); } + if (submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID) + return &submodule->wd_oid; + else + return NULL; +} + +git_submodule_ignore_t git_submodule_ignore(git_submodule *submodule) +{ + assert(submodule); + return submodule->ignore; +} + +git_submodule_ignore_t git_submodule_set_ignore( + git_submodule *submodule, git_submodule_ignore_t ignore) +{ + git_submodule_ignore_t old; + + assert(submodule); + + if (ignore == GIT_SUBMODULE_IGNORE_DEFAULT) + ignore = submodule->ignore_default; + + old = submodule->ignore; + submodule->ignore = ignore; + return old; +} + +git_submodule_update_t git_submodule_update(git_submodule *submodule) +{ + assert(submodule); + return submodule->update; +} + +git_submodule_update_t git_submodule_set_update( + git_submodule *submodule, git_submodule_update_t update) +{ + git_submodule_update_t old; + + assert(submodule); + + if (update == GIT_SUBMODULE_UPDATE_DEFAULT) + update = submodule->update_default; + + old = submodule->update; + submodule->update = update; + return old; +} + +int git_submodule_fetch_recurse_submodules( + git_submodule *submodule) +{ + assert(submodule); + return submodule->fetch_recurse; +} + +int git_submodule_set_fetch_recurse_submodules( + git_submodule *submodule, + int fetch_recurse_submodules) +{ + int old; + + assert(submodule); + + old = submodule->fetch_recurse; + submodule->fetch_recurse = (fetch_recurse_submodules != 0); + return old; +} + +int git_submodule_init(git_submodule *submodule, int overwrite) +{ + int error; + + /* write "submodule.NAME.url" */ + + if (!submodule->url) { + giterr_set(GITERR_SUBMODULE, + "No URL configured for submodule '%s'", submodule->name); + return -1; + } + + error = submodule_update_config( + submodule, "url", submodule->url, overwrite != 0, false); + if (error < 0) + return error; + + /* write "submodule.NAME.update" if not default */ + + if (submodule->update == GIT_SUBMODULE_UPDATE_CHECKOUT) + error = submodule_update_config( + submodule, "update", NULL, (overwrite != 0), false); + else if (submodule->update != GIT_SUBMODULE_UPDATE_DEFAULT) + error = submodule_update_config( + submodule, "update", + _sm_update_map[submodule->update].str_match, + (overwrite != 0), false); + + return error; +} + +int git_submodule_sync(git_submodule *submodule) +{ + if (!submodule->url) { + giterr_set(GITERR_SUBMODULE, + "No URL configured for submodule '%s'", submodule->name); + return -1; + } + + /* copy URL over to config only if it already exists */ + + return submodule_update_config( + submodule, "url", submodule->url, true, true); +} + +int git_submodule_open( + git_repository **subrepo, + git_submodule *submodule) +{ + int error; + git_buf path = GIT_BUF_INIT; + git_repository *repo; + const char *workdir; + + assert(submodule && subrepo); + + repo = submodule->owner; + workdir = git_repository_workdir(repo); + + if (!workdir) { + giterr_set(GITERR_REPOSITORY, + "Cannot open submodule repository in a bare repo"); + return GIT_ENOTFOUND; + } + + if ((submodule->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) { + giterr_set(GITERR_REPOSITORY, + "Cannot open submodule repository that is not checked out"); + return GIT_ENOTFOUND; + } + + if (git_buf_joinpath(&path, workdir, submodule->path) < 0) + return -1; + + error = git_repository_open(subrepo, path.ptr); + + git_buf_free(&path); + + /* if we have opened the submodule successfully, let's grab the HEAD OID */ + if (!error && !(submodule->flags & GIT_SUBMODULE_STATUS__WD_OID_VALID)) { + if (!git_reference_name_to_oid( + &submodule->wd_oid, *subrepo, GIT_HEAD_FILE)) + submodule->flags |= GIT_SUBMODULE_STATUS__WD_OID_VALID; + else + giterr_clear(); + } + + return error; +} + +int git_submodule_reload_all(git_repository *repo) +{ + assert(repo); + return load_submodule_config(repo, true); +} + +int git_submodule_reload(git_submodule *submodule) +{ + git_repository *repo; + git_index *index; + int pos, error; + git_tree *head; + git_config_file *mods; + + assert(submodule); + + /* refresh index data */ + + repo = submodule->owner; + if (git_repository_index__weakptr(&index, repo) < 0) + return -1; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS__INDEX_OID_VALID); + + pos = git_index_find(index, submodule->path); + if (pos >= 0) { + git_index_entry *entry = git_index_get(index, pos); + + if (S_ISGITLINK(entry->mode)) { + if ((error = submodule_load_from_index(repo, entry)) < 0) + return error; + } else { + submodule_mode_mismatch( + repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); + } + } + + /* refresh HEAD tree data */ + + if (!(error = git_repository_head_tree(&head, repo))) { + git_tree_entry *te; + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS__HEAD_OID_VALID); + + if (!(error = git_tree_entry_bypath(&te, head, submodule->path))) { + + if (S_ISGITLINK(te->attr)) { + error = submodule_load_from_head(repo, submodule->path, &te->oid); + } else { + submodule_mode_mismatch( + repo, submodule->path, + GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); + } + + git_tree_entry_free(te); + } + else if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + git_tree_free(head); + } + + if (error < 0) + return error; + + /* refresh config data */ + + if ((mods = open_gitmodules(repo, false, NULL)) != NULL) { + git_buf path = GIT_BUF_INIT; + + git_buf_sets(&path, "submodule\\."); + git_buf_puts_escape_regex(&path, submodule->name); + git_buf_puts(&path, ".*"); + + if (git_buf_oom(&path)) + error = -1; + else + error = git_config_file_foreach_match( + mods, path.ptr, submodule_load_from_config, repo); + + git_buf_free(&path); + git_config_file_free(mods); + } + + if (error < 0) + return error; + + /* refresh wd data */ + + submodule->flags = submodule->flags & + ~(GIT_SUBMODULE_STATUS_IN_WD | GIT_SUBMODULE_STATUS__WD_OID_VALID); + + error = submodule_load_from_wd_lite(submodule, submodule->path, NULL); + + return error; +} + +int git_submodule_status( + unsigned int *status, + git_submodule *submodule) +{ + int error = 0; + unsigned int status_val; + + assert(status && submodule); + + status_val = GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(submodule->flags); + + if (submodule->ignore != GIT_SUBMODULE_IGNORE_ALL) { + if (!(error = submodule_index_status(&status_val, submodule))) + error = submodule_wd_status(&status_val, submodule); + } + + *status = status_val; + + return error; +} + +/* + * INTERNAL FUNCTIONS + */ + +static git_submodule *submodule_alloc(git_repository *repo, const char *name) +{ + git_submodule *sm; + + if (!name || !strlen(name)) { + giterr_set(GITERR_SUBMODULE, "Invalid submodule name"); + return NULL; + } + + sm = git__calloc(1, sizeof(git_submodule)); + if (sm == NULL) + goto fail; + + sm->path = sm->name = git__strdup(name); + if (!sm->name) + goto fail; + + sm->owner = repo; + sm->refcount = 1; + return sm; + +fail: + submodule_release(sm, 0); + return NULL; } static void submodule_release(git_submodule *sm, int decr) @@ -77,73 +865,124 @@ static void submodule_release(git_submodule *sm, int decr) if (!sm) return; - sm->refcount -= decr; + sm->refcount -= decr; + + if (sm->refcount == 0) { + if (sm->name != sm->path) { + git__free(sm->path); + sm->path = NULL; + } + + git__free(sm->name); + sm->name = NULL; + + git__free(sm->url); + sm->url = NULL; + + sm->owner = NULL; + + git__free(sm); + } +} + +static int submodule_get( + git_submodule **sm_ptr, + git_repository *repo, + const char *name, + const char *alternate) +{ + git_strmap *smcfg = repo->submodules; + khiter_t pos; + git_submodule *sm; + int error; + + assert(repo && name); + + pos = git_strmap_lookup_index(smcfg, name); + + if (!git_strmap_valid_index(smcfg, pos) && alternate) + pos = git_strmap_lookup_index(smcfg, alternate); + + if (!git_strmap_valid_index(smcfg, pos)) { + sm = submodule_alloc(repo, name); + + /* insert value at name - if another thread beats us to it, then use + * their record and release our own. + */ + pos = kh_put(str, smcfg, sm->name, &error); + + if (error < 0) { + submodule_release(sm, 1); + sm = NULL; + } else if (error == 0) { + submodule_release(sm, 1); + sm = git_strmap_value_at(smcfg, pos); + } else { + git_strmap_set_value_at(smcfg, pos, sm); + } + } else { + sm = git_strmap_value_at(smcfg, pos); + } + + *sm_ptr = sm; - if (sm->refcount == 0) { - if (sm->name != sm->path) - git__free(sm->path); - git__free(sm->name); - git__free(sm->url); - git__free(sm); - } + return (sm != NULL) ? 0 : -1; } -static int submodule_from_entry( - git_strmap *smcfg, git_index_entry *entry) +static int submodule_load_from_index( + git_repository *repo, const git_index_entry *entry) { git_submodule *sm; - void *old_sm; - khiter_t pos; - int error; - pos = git_strmap_lookup_index(smcfg, entry->path); + if (submodule_get(&sm, repo, entry->path, NULL) < 0) + return -1; - if (git_strmap_valid_index(smcfg, pos)) - sm = git_strmap_value_at(smcfg, pos); - else - sm = submodule_alloc(entry->path); + if (sm->flags & GIT_SUBMODULE_STATUS_IN_INDEX) { + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES; + return 0; + } - git_oid_cpy(&sm->oid, &entry->oid); + sm->flags |= GIT_SUBMODULE_STATUS_IN_INDEX; - if (strcmp(sm->path, entry->path) != 0) { - if (sm->path != sm->name) { - git__free(sm->path); - sm->path = sm->name; - } - sm->path = git__strdup(entry->path); - if (!sm->path) - goto fail; - } + git_oid_cpy(&sm->index_oid, &entry->oid); + sm->flags |= GIT_SUBMODULE_STATUS__INDEX_OID_VALID; - git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); - if (error < 0) - goto fail; - sm->refcount++; + return 0; +} - if (old_sm && ((git_submodule *)old_sm) != sm) { - /* TODO: log warning about multiple entrys for same submodule path */ - submodule_release(old_sm, 1); - } +static int submodule_load_from_head( + git_repository *repo, const char *path, const git_oid *oid) +{ + git_submodule *sm; + + if (submodule_get(&sm, repo, path, NULL) < 0) + return -1; + + sm->flags |= GIT_SUBMODULE_STATUS_IN_HEAD; + + git_oid_cpy(&sm->head_oid, oid); + sm->flags |= GIT_SUBMODULE_STATUS__HEAD_OID_VALID; return 0; +} -fail: - submodule_release(sm, 0); +static int submodule_config_error(const char *property, const char *value) +{ + giterr_set(GITERR_INVALID, + "Invalid value for submodule '%s' property: '%s'", property, value); return -1; } -static int submodule_from_config( +static int submodule_load_from_config( const char *key, const char *value, void *data) { - git_strmap *smcfg = data; - const char *namestart; - const char *property; + git_repository *repo = data; + git_strmap *smcfg = repo->submodules; + const char *namestart, *property, *alternate = NULL; git_buf name = GIT_BUF_INIT; git_submodule *sm; - void *old_sm = NULL; bool is_path; - khiter_t pos; - int error; + int error = 0; if (git__prefixcmp(key, "submodule.") != 0) return 0; @@ -153,235 +992,502 @@ static int submodule_from_config( if (property == NULL) return 0; property++; - is_path = (strcmp(property, "path") == 0); + is_path = (strcasecmp(property, "path") == 0); if (git_buf_set(&name, namestart, property - namestart - 1) < 0) return -1; - pos = git_strmap_lookup_index(smcfg, name.ptr); - if (!git_strmap_valid_index(smcfg, pos) && is_path) - pos = git_strmap_lookup_index(smcfg, value); - if (!git_strmap_valid_index(smcfg, pos)) - sm = submodule_alloc(name.ptr); - else - sm = git_strmap_value_at(smcfg, pos); - if (!sm) - goto fail; + if (submodule_get(&sm, repo, name.ptr, is_path ? value : NULL) < 0) { + git_buf_free(&name); + return -1; + } - if (strcmp(sm->name, name.ptr) != 0) { - assert(sm->path == sm->name); - sm->name = git_buf_detach(&name); + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; - git_strmap_insert2(smcfg, sm->name, sm, old_sm, error); - if (error < 0) - goto fail; - sm->refcount++; + /* Only from config might we get differing names & paths. If so, then + * update the submodule and insert under the alternative key. + */ + + /* TODO: if case insensitive filesystem, then the following strcmps + * should be strcasecmp + */ + + if (strcmp(sm->name, name.ptr) != 0) { + alternate = sm->name = git_buf_detach(&name); + } else if (is_path && value && strcmp(sm->path, value) != 0) { + alternate = sm->path = git__strdup(value); + if (!sm->path) + error = -1; } - else if (is_path && strcmp(sm->path, value) != 0) { - assert(sm->path == sm->name); - sm->path = git__strdup(value); - if (sm->path == NULL) - goto fail; + if (alternate) { + void *old_sm = NULL; + git_strmap_insert2(smcfg, alternate, sm, old_sm, error); - git_strmap_insert2(smcfg, sm->path, sm, old_sm, error); - if (error < 0) - goto fail; - sm->refcount++; + if (error >= 0) + sm->refcount++; /* inserted under a new key */ + + /* if we replaced an old module under this key, release the old one */ + if (old_sm && ((git_submodule *)old_sm) != sm) { + submodule_release(old_sm, 1); + /* TODO: log warning about multiple submodules with same path */ + } } + git_buf_free(&name); + if (error < 0) + return error; - if (old_sm && ((git_submodule *)old_sm) != sm) { - /* TODO: log warning about multiple submodules with same path */ - submodule_release(old_sm, 1); - } + /* TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) + */ if (is_path) return 0; /* copy other properties into submodule entry */ - if (strcmp(property, "url") == 0) { - if (sm->url) { - git__free(sm->url); - sm->url = NULL; - } - if ((sm->url = git__strdup(value)) == NULL) - goto fail; + if (strcasecmp(property, "url") == 0) { + git__free(sm->url); + sm->url = NULL; + + if (value != NULL && (sm->url = git__strdup(value)) == NULL) + return -1; } - else if (strcmp(property, "update") == 0) { + else if (strcasecmp(property, "update") == 0) { int val; if (git_config_lookup_map_value( - _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) { - giterr_set(GITERR_INVALID, - "Invalid value for submodule update property: '%s'", value); - goto fail; - } - sm->update = (git_submodule_update_t)val; + _sm_update_map, ARRAY_SIZE(_sm_update_map), value, &val) < 0) + return submodule_config_error("update", value); + sm->update_default = sm->update = (git_submodule_update_t)val; } - else if (strcmp(property, "fetchRecurseSubmodules") == 0) { - if (git__parse_bool(&sm->fetch_recurse, value) < 0) { - giterr_set(GITERR_INVALID, - "Invalid value for submodule 'fetchRecurseSubmodules' property: '%s'", value); - goto fail; - } + else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { + if (git__parse_bool(&sm->fetch_recurse, value) < 0) + return submodule_config_error("fetchRecurseSubmodules", value); } - else if (strcmp(property, "ignore") == 0) { + else if (strcasecmp(property, "ignore") == 0) { int val; if (git_config_lookup_map_value( - _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) { - giterr_set(GITERR_INVALID, - "Invalid value for submodule ignore property: '%s'", value); - goto fail; - } - sm->ignore = (git_submodule_ignore_t)val; + _sm_ignore_map, ARRAY_SIZE(_sm_ignore_map), value, &val) < 0) + return submodule_config_error("ignore", value); + sm->ignore_default = sm->ignore = (git_submodule_ignore_t)val; } /* ignore other unknown submodule properties */ return 0; +} -fail: - submodule_release(sm, 0); - git_buf_free(&name); - return -1; +static int submodule_load_from_wd_lite( + git_submodule *sm, const char *name, void *payload) +{ + git_repository *repo = git_submodule_owner(sm); + git_buf path = GIT_BUF_INIT; + + GIT_UNUSED(name); + GIT_UNUSED(payload); + + if (git_buf_joinpath(&path, git_repository_workdir(repo), sm->path) < 0) + return -1; + + if (git_path_isdir(path.ptr)) + sm->flags |= GIT_SUBMODULE_STATUS__WD_SCANNED; + + if (git_path_contains(&path, DOT_GIT)) + sm->flags |= GIT_SUBMODULE_STATUS_IN_WD; + + git_buf_free(&path); + + return 0; +} + +static void submodule_mode_mismatch( + git_repository *repo, const char *path, unsigned int flag) +{ + khiter_t pos = git_strmap_lookup_index(repo->submodules, path); + + if (git_strmap_valid_index(repo->submodules, pos)) { + git_submodule *sm = git_strmap_value_at(repo->submodules, pos); + + sm->flags |= flag; + } } -static int load_submodule_config(git_repository *repo) +static int load_submodule_config_from_index( + git_repository *repo, git_oid *gitmodules_oid) { int error; - git_index *index; - unsigned int i, max_i; - git_oid gitmodules_oid; - git_strmap *smcfg; - struct git_config_file *mods = NULL; + git_iterator *i; + const git_index_entry *entry; - if (repo->submodules) - return 0; + if ((error = git_iterator_for_index(&i, repo)) < 0) + return error; - /* submodule data is kept in a hashtable with each submodule stored - * under both its name and its path. These are usually the same, but - * that is not guaranteed. - */ - smcfg = git_strmap_alloc(); - GITERR_CHECK_ALLOC(smcfg); + error = git_iterator_current(i, &entry); - /* scan index for gitmodules (and .gitmodules entry) */ - if ((error = git_repository_index__weakptr(&index, repo)) < 0) - goto cleanup; - memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); - max_i = git_index_entrycount(index); + while (!error && entry != NULL) { - for (i = 0; i < max_i; i++) { - git_index_entry *entry = git_index_get(index, i); if (S_ISGITLINK(entry->mode)) { - if ((error = submodule_from_entry(smcfg, entry)) < 0) - goto cleanup; + error = submodule_load_from_index(repo, entry); + if (error < 0) + break; + } else { + submodule_mode_mismatch( + repo, entry->path, GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE); + + if (strcmp(entry->path, GIT_MODULES_FILE) == 0) + git_oid_cpy(gitmodules_oid, &entry->oid); } - else if (strcmp(entry->path, ".gitmodules") == 0) - git_oid_cpy(&gitmodules_oid, &entry->oid); + + error = git_iterator_advance(i, &entry); } - /* load .gitmodules from workdir if it exists */ - if (git_repository_workdir(repo) != NULL) { - /* look in workdir for .gitmodules */ - git_buf path = GIT_BUF_INIT; - if (!git_buf_joinpath( - &path, git_repository_workdir(repo), ".gitmodules") && - git_path_isfile(path.ptr)) - { - if (!(error = git_config_file__ondisk(&mods, path.ptr))) - error = git_config_file_open(mods); + git_iterator_free(i); + + return error; +} + +static int load_submodule_config_from_head( + git_repository *repo, git_oid *gitmodules_oid) +{ + int error; + git_tree *head; + git_iterator *i; + const git_index_entry *entry; + + if ((error = git_repository_head_tree(&head, repo)) < 0) + return error; + + if ((error = git_iterator_for_tree(&i, repo, head)) < 0) { + git_tree_free(head); + return error; + } + + error = git_iterator_current(i, &entry); + + while (!error && entry != NULL) { + + if (S_ISGITLINK(entry->mode)) { + error = submodule_load_from_head(repo, entry->path, &entry->oid); + if (error < 0) + break; + } else { + submodule_mode_mismatch( + repo, entry->path, GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE); + + if (strcmp(entry->path, GIT_MODULES_FILE) == 0 && + git_oid_iszero(gitmodules_oid)) + git_oid_cpy(gitmodules_oid, &entry->oid); + } + + error = git_iterator_advance(i, &entry); + } + + git_iterator_free(i); + git_tree_free(head); + + return error; +} + +static git_config_file *open_gitmodules( + git_repository *repo, + bool okay_to_create, + const git_oid *gitmodules_oid) +{ + const char *workdir = git_repository_workdir(repo); + git_buf path = GIT_BUF_INIT; + git_config_file *mods = NULL; + + if (workdir != NULL) { + if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) + return NULL; + + if (okay_to_create || git_path_isfile(path.ptr)) { + /* git_config_file__ondisk should only fail if OOM */ + if (git_config_file__ondisk(&mods, path.ptr) < 0) + mods = NULL; + /* open should only fail here if the file is malformed */ + else if (git_config_file_open(mods) < 0) { + git_config_file_free(mods); + mods = NULL; + } } - git_buf_free(&path); } - /* load .gitmodules from object cache if not in workdir */ - if (!error && mods == NULL && !git_oid_iszero(&gitmodules_oid)) { - /* TODO: is it worth loading gitmodules from object cache? */ + if (!mods && gitmodules_oid && !git_oid_iszero(gitmodules_oid)) { + /* TODO: Retrieve .gitmodules content from ODB */ + + /* Should we actually do this? Core git does not, but it means you + * can't really get much information about submodules on bare repos. + */ + } + + git_buf_free(&path); + + return mods; +} + +static int load_submodule_config(git_repository *repo, bool force) +{ + int error; + git_oid gitmodules_oid; + git_buf path = GIT_BUF_INIT; + git_config_file *mods = NULL; + + if (repo->submodules && !force) + return 0; + + memset(&gitmodules_oid, 0, sizeof(gitmodules_oid)); + + /* Submodule data is kept in a hashtable keyed by both name and path. + * These are usually the same, but that is not guaranteed. + */ + if (!repo->submodules) { + repo->submodules = git_strmap_alloc(); + GITERR_CHECK_ALLOC(repo->submodules); } - /* process .gitmodules info */ - if (!error && mods != NULL) - error = git_config_file_foreach(mods, submodule_from_config, smcfg); + /* add submodule information from index */ + + if ((error = load_submodule_config_from_index(repo, &gitmodules_oid)) < 0) + goto cleanup; + + /* add submodule information from HEAD */ + + if ((error = load_submodule_config_from_head(repo, &gitmodules_oid)) < 0) + goto cleanup; + + /* add submodule information from .gitmodules */ + + if ((mods = open_gitmodules(repo, false, &gitmodules_oid)) != NULL) + error = git_config_file_foreach(mods, submodule_load_from_config, repo); + + if (error != 0) + goto cleanup; + + /* shallow scan submodules in work tree */ - /* store submodule config in repo */ - if (!error) - repo->submodules = smcfg; + if (!git_repository_is_bare(repo)) + error = git_submodule_foreach(repo, submodule_load_from_wd_lite, NULL); cleanup: + git_buf_free(&path); + if (mods != NULL) git_config_file_free(mods); + if (error) - git_strmap_free(smcfg); + git_submodule_config_free(repo); + return error; } -void git_submodule_config_free(git_repository *repo) +static int lookup_head_remote(git_buf *url, git_repository *repo) { - git_strmap *smcfg = repo->submodules; - git_submodule *sm; + int error; + git_config *cfg; + git_reference *head = NULL, *remote = NULL; + const char *tgt, *scan; + git_buf key = GIT_BUF_INIT; + + /* 1. resolve HEAD -> refs/heads/BRANCH + * 2. lookup config branch.BRANCH.remote -> ORIGIN + * 3. lookup remote.ORIGIN.url + */ - repo->submodules = NULL; + if ((error = git_repository_config__weakptr(&cfg, repo)) < 0) + return error; - if (smcfg == NULL) - return; + if (git_reference_lookup(&head, repo, GIT_HEAD_FILE) < 0) { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD cannot be resolved"); + error = GIT_ENOTFOUND; + goto cleanup; + } - git_strmap_foreach_value(smcfg, sm, { - submodule_release(sm,1); - }); - git_strmap_free(smcfg); -} + if (git_reference_type(head) != GIT_REF_SYMBOLIC) { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD is not symbolic"); + error = GIT_ENOTFOUND; + goto cleanup; + } -static int submodule_cmp(const void *a, const void *b) -{ - return strcmp(((git_submodule *)a)->name, ((git_submodule *)b)->name); + if ((error = git_branch_tracking(&remote, head)) < 0) + goto cleanup; + + /* remote should refer to something like refs/remotes/ORIGIN/BRANCH */ + + if (git_reference_type(remote) != GIT_REF_SYMBOLIC || + git__prefixcmp(git_reference_target(remote), "refs/remotes/") != 0) + { + giterr_set(GITERR_SUBMODULE, + "Cannot resolve relative URL when HEAD is not symbolic"); + error = GIT_ENOTFOUND; + goto cleanup; + } + + scan = tgt = git_reference_target(remote) + strlen("refs/remotes/"); + while (*scan && (*scan != '/' || (scan > tgt && scan[-1] != '\\'))) + scan++; /* find non-escaped slash to end ORIGIN name */ + + error = git_buf_printf(&key, "remote.%.*s.url", (int)(scan - tgt), tgt); + if (error < 0) + goto cleanup; + + if ((error = git_config_get_string(&tgt, cfg, key.ptr)) < 0) + goto cleanup; + + error = git_buf_sets(url, tgt); + +cleanup: + git_buf_free(&key); + git_reference_free(head); + git_reference_free(remote); + + return error; } -int git_submodule_foreach( - git_repository *repo, - int (*callback)(const char *name, void *payload), - void *payload) +static int submodule_update_config( + git_submodule *submodule, + const char *attr, + const char *value, + bool overwrite, + bool only_existing) { int error; - git_submodule *sm; - git_vector seen = GIT_VECTOR_INIT; - seen._cmp = submodule_cmp; + git_config *config; + git_buf key = GIT_BUF_INIT; + const char *old = NULL; + + assert(submodule); - if ((error = load_submodule_config(repo)) < 0) + error = git_repository_config__weakptr(&config, submodule->owner); + if (error < 0) return error; - git_strmap_foreach_value(repo->submodules, sm, { - /* usually the following will not come into play */ - if (sm->refcount > 1) { - if (git_vector_bsearch(&seen, sm) != GIT_ENOTFOUND) - continue; - if ((error = git_vector_insert(&seen, sm)) < 0) - break; - } + error = git_buf_printf(&key, "submodule.%s.%s", submodule->name, attr); + if (error < 0) + goto cleanup; - if ((error = callback(sm->name, payload)) < 0) - break; - }); + if (git_config_get_string(&old, config, key.ptr) < 0) + giterr_clear(); - git_vector_free(&seen); + if (!old && only_existing) + goto cleanup; + if (old && !overwrite) + goto cleanup; + if ((!old && !value) || (old && value && strcmp(old, value) == 0)) + goto cleanup; + if (!value) + error = git_config_delete(config, key.ptr); + else + error = git_config_set_string(config, key.ptr, value); + +cleanup: + git_buf_free(&key); return error; } -int git_submodule_lookup( - git_submodule **sm_ptr, /* NULL allowed if user only wants to test */ - git_repository *repo, - const char *name) /* trailing slash is allowed */ +static int submodule_index_status(unsigned int *status, git_submodule *sm) { - khiter_t pos; + const git_oid *head_oid = git_submodule_head_oid(sm); + const git_oid *index_oid = git_submodule_index_oid(sm); - if (load_submodule_config(repo) < 0) - return -1; + if (!head_oid) { + if (index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_ADDED; + } + else if (!index_oid) + *status |= GIT_SUBMODULE_STATUS_INDEX_DELETED; + else if (!git_oid_equal(head_oid, index_oid)) + *status |= GIT_SUBMODULE_STATUS_INDEX_MODIFIED; - pos = git_strmap_lookup_index(repo->submodules, name); - if (!git_strmap_valid_index(repo->submodules, pos)) - return GIT_ENOTFOUND; + return 0; +} - if (sm_ptr) - *sm_ptr = git_strmap_value_at(repo->submodules, pos); +static int submodule_wd_status(unsigned int *status, git_submodule *sm) +{ + int error = 0; + const git_oid *wd_oid, *index_oid; + git_repository *sm_repo = NULL; + + /* open repo now if we need it (so wd_oid() call won't reopen) */ + if ((sm->ignore == GIT_SUBMODULE_IGNORE_NONE || + sm->ignore == GIT_SUBMODULE_IGNORE_UNTRACKED) && + (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) != 0) + { + if ((error = git_submodule_open(&sm_repo, sm)) < 0) + return error; + } - return 0; + index_oid = git_submodule_index_oid(sm); + wd_oid = git_submodule_wd_oid(sm); + + if (!index_oid) { + if (wd_oid) + *status |= GIT_SUBMODULE_STATUS_WD_ADDED; + } + else if (!wd_oid) { + if ((sm->flags & GIT_SUBMODULE_STATUS__WD_SCANNED) != 0 && + (sm->flags & GIT_SUBMODULE_STATUS_IN_WD) == 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNINITIALIZED; + else + *status |= GIT_SUBMODULE_STATUS_WD_DELETED; + } + else if (!git_oid_equal(index_oid, wd_oid)) + *status |= GIT_SUBMODULE_STATUS_WD_MODIFIED; + + if (sm_repo != NULL) { + git_tree *sm_head; + git_diff_options opt; + git_diff_list *diff; + + /* the diffs below could be optimized with an early termination + * option to the git_diff functions, but for now this is sufficient + * (and certainly no worse that what core git does). + */ + + /* perform head-to-index diff on submodule */ + + if ((error = git_repository_head_tree(&sm_head, sm_repo)) < 0) + return error; + + memset(&opt, 0, sizeof(opt)); + 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); + + if (!error) { + if (git_diff_entrycount(diff, -1) > 0) + *status |= GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED; + + git_diff_list_free(diff); + diff = NULL; + } + + git_tree_free(sm_head); + + if (error < 0) + return error; + + /* perform index-to-workdir diff on submodule */ + + error = git_diff_workdir_to_index(sm_repo, &opt, &diff); + + if (!error) { + int untracked = git_diff_entrycount(diff, GIT_DELTA_UNTRACKED); + + if (untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_UNTRACKED; + + if (git_diff_entrycount(diff, -1) - untracked > 0) + *status |= GIT_SUBMODULE_STATUS_WD_WD_MODIFIED; + + git_diff_list_free(diff); + diff = NULL; + } + + git_repository_free(sm_repo); + } + + return error; } diff --git a/src/submodule.h b/src/submodule.h new file mode 100644 index 00000000000..c7a6aaf763b --- /dev/null +++ b/src/submodule.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_submodule_h__ +#define INCLUDE_submodule_h__ + +/* Notes: + * + * Submodule information can be in four places: the index, the config files + * (both .git/config and .gitmodules), the HEAD tree, and the working + * directory. + * + * In the index: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the HEAD tree: + * - submodule is found by path + * - may be missing, present, or of the wrong type + * - will have an oid if present + * + * In the config files: + * - submodule is found by submodule "name" which is usually the path + * - may be missing or present + * - will have a name, path, url, and other properties + * + * In the working directory: + * - submodule is found by path + * - may be missing, an empty directory, a checked out directory, + * or of the wrong type + * - if checked out, will have a HEAD oid + * - if checked out, will have git history that can be used to compare oids + * - if checked out, may have modified files and/or untracked files + */ + +/** + * Description of submodule + * + * This record describes a submodule found in a repository. There should be + * an entry for every submodule found in the HEAD and index, and for every + * submodule described in .gitmodules. The fields are as follows: + * + * - `owner` is the git_repository containing this submodule + * - `name` is the name of the submodule from .gitmodules. + * - `path` is the path to the submodule from the repo root. It is almost + * always the same as `name`. + * - `url` is the url for the submodule. + * - `tree_oid` is the SHA1 for the submodule path in the repo HEAD. + * - `index_oid` is the SHA1 for the submodule recorded in the index. + * - `workdir_oid` is the SHA1 for the HEAD of the checked out submodule. + * - `update` is a git_submodule_update_t value - see gitmodules(5) update. + * - `ignore` is a git_submodule_ignore_t value - see gitmodules(5) ignore. + * - `fetch_recurse` is 0 or 1 - see gitmodules(5) fetchRecurseSubmodules. + * - `refcount` tracks how many hashmap entries there are for this submodule. + * It only comes into play if the name and path of the submodule differ. + * - `flags` is for internal use, tracking where this submodule has been + * found (head, index, config, workdir) and other misc info about it. + * + * If the submodule has been added to .gitmodules but not yet git added, + * then the `index_oid` will be valid and zero. If the submodule has been + * deleted, but the delete has not been committed yet, then the `index_oid` + * will be set, but the `url` will be NULL. + */ +struct git_submodule { + git_repository *owner; + char *name; + char *path; /* important: may point to same string data as "name" */ + char *url; + uint32_t flags; + git_oid head_oid; + git_oid index_oid; + git_oid wd_oid; + /* information from config */ + git_submodule_update_t update; + git_submodule_update_t update_default; + git_submodule_ignore_t ignore; + git_submodule_ignore_t ignore_default; + int fetch_recurse; + /* internal information */ + int refcount; +}; + +/* Additional flags on top of public GIT_SUBMODULE_STATUS values */ +enum { + GIT_SUBMODULE_STATUS__WD_SCANNED = (1u << 20), + GIT_SUBMODULE_STATUS__HEAD_OID_VALID = (1u << 21), + GIT_SUBMODULE_STATUS__INDEX_OID_VALID = (1u << 22), + GIT_SUBMODULE_STATUS__WD_OID_VALID = (1u << 23), + GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE = (1u << 24), + GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE = (1u << 25), + GIT_SUBMODULE_STATUS__WD_NOT_SUBMODULE = (1u << 26), + GIT_SUBMODULE_STATUS__INDEX_MULTIPLE_ENTRIES = (1u << 27), +}; + +#define GIT_SUBMODULE_STATUS__CLEAR_INTERNAL(S) \ + ((S) & ~(0xFFFFFFFFu << 20)) + +#endif diff --git a/src/tag.c b/src/tag.c index 463619f63e7..6495d470f01 100644 --- a/src/tag.c +++ b/src/tag.c @@ -445,20 +445,5 @@ int git_tag_list(git_strarray *tag_names, git_repository *repo) int git_tag_peel(git_object **tag_target, git_tag *tag) { - int error; - git_object *target; - - assert(tag_target && tag); - - if (git_tag_target(&target, tag) < 0) - return -1; - - if (git_object_type(target) == GIT_OBJ_TAG) { - error = git_tag_peel(tag_target, (git_tag *)target); - git_object_free(target); - return error; - } - - *tag_target = target; - return 0; + return git_object_peel(tag_target, (git_object *)tag, GIT_OBJ_ANY); } diff --git a/src/transport.h b/src/transport.h index 68b92f7a612..ff3a58d1344 100644 --- a/src/transport.h +++ b/src/transport.h @@ -12,6 +12,7 @@ #include "vector.h" #include "posix.h" #include "common.h" +#include "netops.h" #ifdef GIT_SSL # include # include @@ -19,10 +20,16 @@ #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" typedef struct git_transport_caps { int common:1, - ofs_delta:1; + ofs_delta:1, + multi_ack: 1, + side_band:1, + side_band_64k:1; } git_transport_caps; #ifdef GIT_SSL @@ -70,19 +77,26 @@ struct git_transport { int direction : 1, /* 0 fetch, 1 push */ connected : 1, check_cert: 1, - encrypt : 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; /** * Connect and store the remote heads */ int (*connect)(struct git_transport *transport, int dir); /** - * Give a list of references, useful for ls-remote + * Send our side of a negotiation */ - int (*ls)(struct git_transport *transport, git_headlist_cb list_cb, void *opaque); + int (*negotiation_step)(struct git_transport *transport, void *data, size_t len); /** * Push the changes over */ @@ -96,10 +110,6 @@ struct git_transport { * Download the packfile */ int (*download_pack)(struct git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats); - /** - * Fetch the changes - */ - int (*fetch)(struct git_transport *transport); /** * Close the connection */ @@ -108,6 +118,11 @@ struct git_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); }; @@ -124,7 +139,6 @@ int git_transport_dummy(struct git_transport **transport); */ int git_transport_valid_url(const char *url); -typedef struct git_transport git_transport; typedef int (*git_transport_cb)(git_transport **transport); #endif diff --git a/src/transports/git.c b/src/transports/git.c index 45f571f20c2..b757495c5be 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -24,12 +24,7 @@ typedef struct { git_transport parent; - git_protocol proto; - git_vector refs; - git_remote_head **heads; - git_transport_caps caps; - char buff[1024]; - gitno_buffer buf; + char buff[65536]; #ifdef GIT_WIN32 WSADATA wsd; #endif @@ -126,68 +121,6 @@ static int do_connect(transport_git *t, const char *url) return -1; } -/* - * Read from the socket and store the references in the vector - */ -static int store_refs(transport_git *t) -{ - gitno_buffer *buf = &t->buf; - int ret = 0; - - while (1) { - if ((ret = gitno_recv(buf)) < 0) - return -1; - if (ret == 0) /* Orderly shutdown, so exit */ - return 0; - - ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset); - if (ret == GIT_EBUFS) { - gitno_consume_n(buf, buf->len); - continue; - } - - if (ret < 0) - return ret; - - gitno_consume_n(buf, buf->offset); - - if (t->proto.flush) { /* No more refs */ - t->proto.flush = 0; - return 0; - } - } -} - -static int detect_caps(transport_git *t) -{ - git_vector *refs = &t->refs; - git_pkt_ref *pkt; - git_transport_caps *caps = &t->caps; - const char *ptr; - - pkt = git_vector_get(refs, 0); - /* No refs or capabilites, odd but not a problem */ - if (pkt == NULL || pkt->capabilities == NULL) - return 0; - - ptr = pkt->capabilities; - while (ptr != NULL && *ptr != '\0') { - if (*ptr == ' ') - ptr++; - - if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { - caps->common = caps->ofs_delta = 1; - ptr += strlen(GIT_CAP_OFS_DELTA); - continue; - } - - /* We don't know this capability, so skip it */ - ptr = strchr(ptr, ' '); - } - - return 0; -} - /* * Since this is a network connection, we need to parse and store the * pkt-lines at this stage and keep them there. @@ -202,202 +135,26 @@ static int git_connect(git_transport *transport, int direction) } t->parent.direction = direction; - if (git_vector_init(&t->refs, 16, NULL) < 0) - return -1; /* Connect and ask for the refs */ if (do_connect(t, transport->url) < 0) - goto cleanup; + return -1; - gitno_buffer_setup(transport, &t->buf, t->buff, sizeof(t->buff)); + gitno_buffer_setup(transport, &transport->buffer, t->buff, sizeof(t->buff)); t->parent.connected = 1; - if (store_refs(t) < 0) - goto cleanup; - - if (detect_caps(t) < 0) - goto cleanup; - - return 0; -cleanup: - git_vector_free(&t->refs); - return -1; -} - -static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) -{ - transport_git *t = (transport_git *) transport; - git_vector *refs = &t->refs; - unsigned int i; - git_pkt *p = NULL; - - git_vector_foreach(refs, i, p) { - git_pkt_ref *pkt = NULL; - - if (p->type != GIT_PKT_REF) - continue; - - pkt = (git_pkt_ref *)p; - - if (list_cb(&pkt->head, opaque) < 0) { - giterr_set(GITERR_NET, "User callback returned error"); - return -1; - } - } - - return 0; -} - -/* Wait until we get an ack from the */ -static int recv_pkt(gitno_buffer *buf) -{ - const char *ptr = buf->data, *line_end; - git_pkt *pkt; - int pkt_type, error; - - do { - /* Wait for max. 1 second */ - if ((error = gitno_select_in(buf, 1, 0)) < 0) { - return -1; - } else if (error == 0) { - /* - * Some servers don't respond immediately, so if this - * happens, we keep sending information until it - * answers. Pretend we received a NAK to convince higher - * layers to do so. - */ - return GIT_PKT_NAK; - } - - if ((error = gitno_recv(buf)) < 0) - return -1; - - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); - if (error == GIT_EBUFS) - continue; - if (error < 0) - return -1; - } while (error); - - gitno_consume(buf, line_end); - pkt_type = pkt->type; - git__free(pkt); - - return pkt_type; -} - -static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) -{ - transport_git *t = (transport_git *) transport; - git_revwalk *walk; - git_oid oid; - int error; - unsigned int i; - git_buf data = GIT_BUF_INIT; - gitno_buffer *buf = &t->buf; - - if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0) + if (git_protocol_store_refs(transport, 1) < 0) return -1; - if (git_fetch_setup_walk(&walk, repo) < 0) - goto on_error; - - if (gitno_send(transport, data.ptr, data.size, 0) < 0) - goto on_error; - - git_buf_clear(&data); - /* - * We don't support any kind of ACK extensions, so the negotiation - * boils down to sending what we have and listening for an ACK - * every once in a while. - */ - i = 0; - while ((error = git_revwalk_next(&oid, walk)) == 0) { - git_pkt_buffer_have(&oid, &data); - i++; - if (i % 20 == 0) { - int pkt_type; - - git_pkt_buffer_flush(&data); - if (git_buf_oom(&data)) - goto on_error; - - if (gitno_send(transport, data.ptr, data.size, 0) < 0) - goto on_error; - - pkt_type = recv_pkt(buf); - - if (pkt_type == GIT_PKT_ACK) { - break; - } else if (pkt_type == GIT_PKT_NAK) { - continue; - } else { - giterr_set(GITERR_NET, "Unexpected pkt type"); - goto on_error; - } - - } - } - if (error < 0 && error != GIT_REVWALKOVER) - goto on_error; - - /* Tell the other end that we're done negotiating */ - git_buf_clear(&data); - git_pkt_buffer_flush(&data); - git_pkt_buffer_done(&data); - if (gitno_send(transport, data.ptr, data.size, 0) < 0) - goto on_error; + if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) + return -1; - git_buf_free(&data); - git_revwalk_free(walk); return 0; - -on_error: - git_buf_free(&data); - git_revwalk_free(walk); - return -1; } -static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) +static int git_negotiation_step(struct git_transport *transport, void *data, size_t len) { - transport_git *t = (transport_git *) transport; - int error = 0, read_bytes; - gitno_buffer *buf = &t->buf; - git_pkt *pkt; - const char *line_end, *ptr; - - /* - * For now, we ignore everything and wait for the pack - */ - do { - ptr = buf->data; - /* Whilst we're searching for the pack */ - while (1) { - if (buf->offset == 0) { - break; - } - - error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); - if (error == GIT_EBUFS) - break; - - if (error < 0) - return error; - - if (pkt->type == GIT_PKT_PACK) { - git__free(pkt); - return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats); - } - - /* For now we don't care about anything */ - git__free(pkt); - gitno_consume(buf, line_end); - } - - read_bytes = gitno_recv(buf); - } while (read_bytes); - - return read_bytes; + return gitno_send(transport, data, len, 0); } static int git_close(git_transport *t) @@ -408,6 +165,7 @@ static int git_close(git_transport *t) 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 (gitno_close(t->socket) < 0) { giterr_set(GITERR_NET, "Failed to close socket"); @@ -426,17 +184,22 @@ static int git_close(git_transport *t) static void git_free(git_transport *transport) { transport_git *t = (transport_git *) transport; - git_vector *refs = &t->refs; + git_vector *refs = &transport->refs; unsigned int i; for (i = 0; i < refs->length; ++i) { git_pkt *p = git_vector_get(refs, i); git_pkt_free(p); } + git_vector_free(refs); + refs = &transport->common; + for (i = 0; i < refs->length; ++i) { + git_pkt *p = git_vector_get(refs, i); + git_pkt_free(p); + } git_vector_free(refs); - git__free(t->heads); - git_buf_free(&t->proto.buf); + git__free(t->parent.url); git__free(t); } @@ -452,15 +215,16 @@ int git_transport_git(git_transport **out) GITERR_CHECK_ALLOC(t); memset(t, 0x0, sizeof(transport_git)); + if (git_vector_init(&t->parent.common, 8, NULL)) + goto on_error; + + if (git_vector_init(&t->parent.refs, 16, NULL) < 0) + goto on_error; t->parent.connect = git_connect; - t->parent.ls = git_ls; - t->parent.negotiate_fetch = git_negotiate_fetch; - t->parent.download_pack = git_download_pack; + t->parent.negotiation_step = git_negotiation_step; t->parent.close = git_close; t->parent.free = git_free; - t->proto.refs = &t->refs; - t->proto.transport = (git_transport *) t; *out = (git_transport *) t; @@ -474,4 +238,8 @@ int git_transport_git(git_transport **out) #endif return 0; + +on_error: + git__free(t); + return -1; } diff --git a/src/transports/http.c b/src/transports/http.c index f25d639f3e9..d5015f5af4a 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -4,7 +4,6 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ - #include #include "git2.h" #include "http_parser.h" @@ -20,6 +19,13 @@ #include "filebuf.h" #include "repository.h" #include "protocol.h" +#if GIT_WINHTTP +# include +# pragma comment(lib, "winhttp.lib") +#endif + +#define WIDEN2(s) L ## s +#define WIDEN(s) WIDEN2(s) enum last_cb { NONE, @@ -29,11 +35,8 @@ enum last_cb { typedef struct { git_transport parent; - git_protocol proto; - git_vector refs; - git_vector common; + http_parser_settings settings; git_buf buf; - git_remote_head **heads; int error; int transfer_finished :1, ct_found :1, @@ -46,10 +49,15 @@ typedef struct { char *host; char *port; char *service; - git_transport_caps caps; + 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, @@ -80,17 +88,152 @@ static int gen_request(git_buf *buf, const char *path, const char *host, const c return 0; } -static int do_connect(transport_http *t, const char *host, const char *port) +static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls) { +#ifndef GIT_WINHTTP + git_buf request = GIT_BUF_INIT; + const char *verb; + + 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; + } + + + if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0) { + git_buf_free(&request); + return -1; + } + + if (content_length) { + if (gitno_send((git_transport *) t, data, content_length, 0) < 0) + return -1; + } + + return 0; +#else + wchar_t *verb; + wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH]; + 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; + + git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf)); + + 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; + } + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0) + goto on_error; + + git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf)); + + 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; + } + + 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; + } + } + + 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; + } + + ret = WinHttpReceiveResponse(t->request, NULL); + if (ret == FALSE) { + giterr_set(GITERR_OS, "Failed to receive response"); + goto on_error; + } + + return 0; + +on_error: + git_buf_free(&buf); + if (t->request) + WinHttpCloseHandle(t->request); + t->request = NULL; + return -1; +#endif +} + +static int do_connect(transport_http *t) +{ +#ifndef GIT_WINHTTP if (t->parent.connected && http_should_keep_alive(&t->parser)) return 0; - if (gitno_connect((git_transport *) t, host, port) < 0) + if (gitno_connect((git_transport *) t, t->host, t->port) < 0) return -1; t->parent.connected = 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); + + if (t->session == NULL) { + giterr_set(GITERR_OS, "Failed to init WinHTTP"); + goto on_error; + } + + 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; + } + + t->parent.connected = 1; + return 0; + +on_error: + if (t->session) { + WinHttpCloseHandle(t->session); + t->session = NULL; + } + return -1; +#endif } /* @@ -183,17 +326,6 @@ static int on_headers_complete(http_parser *parser) return 0; } -static int on_body_store_refs(http_parser *parser, const char *str, size_t len) -{ - transport_http *t = (transport_http *) parser->data; - - if (parser->status_code == 404) { - return git_buf_put(&t->buf, str, len); - } - - return git_protocol_store_refs(&t->proto, str, len); -} - static int on_message_complete(http_parser *parser) { transport_http *t = (transport_http *) parser->data; @@ -208,51 +340,87 @@ static int on_message_complete(http_parser *parser) return 0; } -static int store_refs(transport_http *t) +static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len) { - http_parser_settings settings; - char buffer[1024]; - gitno_buffer buf; - git_pkt *pkt; - int ret; + git_transport *transport = (git_transport *) parser->data; + transport_http *t = (transport_http *) parser->data; + gitno_buffer *buf = &transport->buffer; - http_parser_init(&t->parser, HTTP_RESPONSE); - t->parser.data = t; - memset(&settings, 0x0, sizeof(http_parser_settings)); - settings.on_header_field = on_header_field; - settings.on_header_value = on_header_value; - settings.on_headers_complete = on_headers_complete; - settings.on_body = on_body_store_refs; - settings.on_message_complete = on_message_complete; + if (buf->len - buf->offset < len) { + giterr_set(GITERR_NET, "Can't fit data in the buffer"); + return t->error = -1; + } - gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); + memcpy(buf->data + buf->offset, str, len); + buf->offset += len; - while(1) { - size_t parsed; + return 0; +} - if ((ret = gitno_recv(&buf)) < 0) - return -1; +static int http_recv_cb(gitno_buffer *buf) +{ + 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 - parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); - /* Both should happen at the same time */ - if (parsed != buf.offset || t->error < 0) - return t->error; + if (t->transfer_finished) + return 0; + +#ifndef GIT_WINHTTP + gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer)); - gitno_consume_n(&buf, parsed); + if ((error = gitno_recv(&inner)) < 0) + return -1; - if (ret == 0 || t->transfer_finished) - return 0; + 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; } - pkt = git_vector_get(&t->refs, 0); - if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) { - giterr_set(GITERR_NET, "Invalid HTTP response"); + if (buf->len - buf->offset < recvd) { + giterr_set(GITERR_NET, "Can't fit data in the buffer"); return t->error = -1; - } else { - git_vector_remove(&t->refs, 0); } - return 0; + memcpy(buf->data + buf->offset, buffer, recvd); + buf->offset += recvd; +#endif + + return (int)(buf->offset - old_len); +} + +/* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */ +static void setup_gitno_buffer(git_transport *transport) +{ + transport_http *t = (transport_http *) transport; + + /* 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 + + gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t); } static int http_connect(git_transport *transport, int direction) @@ -263,6 +431,7 @@ static int http_connect(git_transport *transport, int direction) const char *service = "upload-pack"; const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://"; const char *default_port; + git_pkt *pkt; if (direction == GIT_DIR_PUSH) { giterr_set(GITERR_NET, "Pushing over HTTP is not implemented"); @@ -270,8 +439,6 @@ static int http_connect(git_transport *transport, int direction) } t->parent.direction = direction; - if (git_vector_init(&t->refs, 16, NULL) < 0) - return -1; if (!git__prefixcmp(url, prefix_http)) { url = t->parent.url + strlen(prefix_http); @@ -291,20 +458,31 @@ static int http_connect(git_transport *transport, int direction) t->service = git__strdup(service); GITERR_CHECK_ALLOC(t->service); - if ((ret = do_connect(t, t->host, t->port)) < 0) + if ((ret = do_connect(t)) < 0) goto cleanup; - /* Generate and send the HTTP request */ - if ((ret = gen_request(&request, t->path, t->host, "GET", service, 0, 1)) < 0) { - giterr_set(GITERR_NET, "Failed to generate request"); + if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0) goto cleanup; - } - - if (gitno_send(transport, request.ptr, request.size, 0) < 0) + setup_gitno_buffer(transport); + if ((ret = git_protocol_store_refs(transport, 2)) < 0) goto cleanup; - ret = store_refs(t); + 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 and flush pkts */ + git_vector_remove(&transport->refs, 0); + git__free(pkt); + pkt = git_vector_get(&transport->refs, 0); + git_vector_remove(&transport->refs, 0); + git__free(pkt); + } + + if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0) + return t->error = -1; cleanup: git_buf_free(&request); @@ -313,303 +491,27 @@ static int http_connect(git_transport *transport, int direction) return ret; } -static int http_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) -{ - transport_http *t = (transport_http *) transport; - git_vector *refs = &t->refs; - unsigned int i; - git_pkt_ref *p; - - git_vector_foreach(refs, i, p) { - if (p->type != GIT_PKT_REF) - continue; - - if (list_cb(&p->head, opaque) < 0) { - giterr_set(GITERR_NET, "The user callback returned error"); - return -1; - } - } - - return 0; -} - -static int on_body_parse_response(http_parser *parser, const char *str, size_t len) -{ - transport_http *t = (transport_http *) parser->data; - git_buf *buf = &t->buf; - git_vector *common = &t->common; - int error; - const char *line_end, *ptr; - - if (len == 0) { /* EOF */ - if (git_buf_len(buf) != 0) { - giterr_set(GITERR_NET, "Unexpected EOF"); - return t->error = -1; - } else { - return 0; - } - } - - git_buf_put(buf, str, len); - ptr = buf->ptr; - while (1) { - git_pkt *pkt; - - if (git_buf_len(buf) == 0) - return 0; - - error = git_pkt_parse_line(&pkt, ptr, &line_end, git_buf_len(buf)); - if (error == GIT_EBUFS) { - return 0; /* Ask for more */ - } - if (error < 0) - return t->error = -1; - - git_buf_consume(buf, line_end); - - if (pkt->type == GIT_PKT_PACK) { - git__free(pkt); - t->pack_ready = 1; - return 0; - } - - if (pkt->type == GIT_PKT_NAK) { - git__free(pkt); - return 0; - } - - if (pkt->type != GIT_PKT_ACK) { - git__free(pkt); - continue; - } - - if (git_vector_insert(common, pkt) < 0) - return -1; - } - - return error; - -} - -static int parse_response(transport_http *t) -{ - int ret = 0; - http_parser_settings settings; - char buffer[1024]; - gitno_buffer buf; - - http_parser_init(&t->parser, HTTP_RESPONSE); - t->parser.data = t; - t->transfer_finished = 0; - memset(&settings, 0x0, sizeof(http_parser_settings)); - settings.on_header_field = on_header_field; - settings.on_header_value = on_header_value; - settings.on_headers_complete = on_headers_complete; - settings.on_body = on_body_parse_response; - settings.on_message_complete = on_message_complete; - - gitno_buffer_setup((git_transport *)t, &buf, buffer, sizeof(buffer)); - - while(1) { - size_t parsed; - - if ((ret = gitno_recv(&buf)) < 0) - return -1; - - parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); - /* Both should happen at the same time */ - if (parsed != buf.offset || t->error < 0) - return t->error; - - gitno_consume_n(&buf, parsed); - - if (ret == 0 || t->transfer_finished || t->pack_ready) { - return 0; - } - } - - return ret; -} - -static int http_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) +static int http_negotiation_step(struct git_transport *transport, void *data, size_t len) { transport_http *t = (transport_http *) transport; int ret; - unsigned int i; - char buff[128]; - gitno_buffer buf; - git_revwalk *walk = NULL; - git_oid oid; - git_pkt_ack *pkt; - git_vector *common = &t->common; - git_buf request = GIT_BUF_INIT, data = GIT_BUF_INIT; - - gitno_buffer_setup(transport, &buf, buff, sizeof(buff)); - - if (git_vector_init(common, 16, NULL) < 0) - return -1; - - if (git_fetch_setup_walk(&walk, repo) < 0) - return -1; - - do { - if ((ret = do_connect(t, t->host, t->port)) < 0) - goto cleanup; - if ((ret = git_pkt_buffer_wants(wants, &t->caps, &data)) < 0) - goto cleanup; - - /* We need to send these on each connection */ - git_vector_foreach (common, i, pkt) { - if ((ret = git_pkt_buffer_have(&pkt->oid, &data)) < 0) - goto cleanup; - } - - i = 0; - while ((i < 20) && ((ret = git_revwalk_next(&oid, walk)) == 0)) { - if ((ret = git_pkt_buffer_have(&oid, &data)) < 0) - goto cleanup; - - i++; - } - - git_pkt_buffer_done(&data); - - if ((ret = gen_request(&request, t->path, t->host, "POST", "upload-pack", data.size, 0)) < 0) - goto cleanup; - - if ((ret = gitno_send(transport, request.ptr, request.size, 0)) < 0) - goto cleanup; - - if ((ret = gitno_send(transport, data.ptr, data.size, 0)) < 0) - goto cleanup; - - git_buf_clear(&request); - git_buf_clear(&data); - - if (ret < 0 || i >= 256) - break; - - if ((ret = parse_response(t)) < 0) - goto cleanup; - - if (t->pack_ready) { - ret = 0; - goto cleanup; - } - - } while(1); - -cleanup: - git_buf_free(&request); - git_buf_free(&data); - git_revwalk_free(walk); - return ret; -} - -typedef struct { - git_indexer_stream *idx; - git_indexer_stats *stats; - transport_http *transport; -} download_pack_cbdata; - -static int on_message_complete_download_pack(http_parser *parser) -{ - download_pack_cbdata *data = (download_pack_cbdata *) parser->data; - - data->transport->transfer_finished = 1; - - return 0; -} -static int on_body_download_pack(http_parser *parser, const char *str, size_t len) -{ - download_pack_cbdata *data = (download_pack_cbdata *) parser->data; - transport_http *t = data->transport; - git_indexer_stream *idx = data->idx; - git_indexer_stats *stats = data->stats; - - return t->error = git_indexer_stream_add(idx, str, len, stats); -} - -/* - * As the server is probably using Transfer-Encoding: chunked, we have - * to use the HTTP parser to download the pack instead of giving it to - * the simple downloader. Furthermore, we're using keep-alive - * connections, so the simple downloader would just hang. - */ -static int http_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats) -{ - transport_http *t = (transport_http *) transport; - git_buf *oldbuf = &t->buf; - int recvd; - http_parser_settings settings; - char buffer[1024]; - gitno_buffer buf; - git_buf path = GIT_BUF_INIT; - git_indexer_stream *idx = NULL; - download_pack_cbdata data; - - gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); - - if (memcmp(oldbuf->ptr, "PACK", strlen("PACK"))) { - giterr_set(GITERR_NET, "The pack doesn't start with a pack signature"); - return -1; - } - - if (git_buf_joinpath(&path, git_repository_path(repo), "objects/pack") < 0) + /* First, send the data as a HTTP POST request */ + if ((ret = do_connect(t)) < 0) return -1; - if (git_indexer_stream_new(&idx, git_buf_cstr(&path)) < 0) + if (send_request(t, "upload-pack", data, len, 0) < 0) return -1; - /* - * This is part of the previous response, so we don't want to - * re-init the parser, just set these two callbacks. - */ - memset(stats, 0, sizeof(git_indexer_stats)); - data.stats = stats; - data.idx = idx; - data.transport = t; - t->parser.data = &data; - t->transfer_finished = 0; - memset(&settings, 0x0, sizeof(settings)); - settings.on_message_complete = on_message_complete_download_pack; - settings.on_body = on_body_download_pack; - *bytes = git_buf_len(oldbuf); - - if (git_indexer_stream_add(idx, git_buf_cstr(oldbuf), git_buf_len(oldbuf), stats) < 0) - goto on_error; - - gitno_buffer_setup(transport, &buf, buffer, sizeof(buffer)); - - do { - size_t parsed; - - if ((recvd = gitno_recv(&buf)) < 0) - goto on_error; - - parsed = http_parser_execute(&t->parser, &settings, buf.data, buf.offset); - if (parsed != buf.offset || t->error < 0) - goto on_error; - - *bytes += recvd; - gitno_consume_n(&buf, parsed); - } while (recvd > 0 && !t->transfer_finished); + /* Then we need to set up the buffer to grab data from the HTTP response */ + setup_gitno_buffer(transport); - if (git_indexer_stream_finalize(idx, stats) < 0) - goto on_error; - - git_indexer_stream_free(idx); return 0; - -on_error: - git_indexer_stream_free(idx); - git_buf_free(&path); - return -1; } static int http_close(git_transport *transport) { +#ifndef GIT_WINHTTP if (gitno_ssl_teardown(transport) < 0) return -1; @@ -617,6 +519,16 @@ static int http_close(git_transport *transport) giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno)); return -1; } +#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 transport->connected = 0; @@ -627,8 +539,8 @@ static int http_close(git_transport *transport) static void http_free(git_transport *transport) { transport_http *t = (transport_http *) transport; - git_vector *refs = &t->refs; - git_vector *common = &t->common; + git_vector *refs = &transport->refs; + git_vector *common = &transport->common; unsigned int i; git_pkt *p; @@ -649,8 +561,6 @@ static void http_free(git_transport *transport) } git_vector_free(common); git_buf_free(&t->buf); - git_buf_free(&t->proto.buf); - git__free(t->heads); git__free(t->content_type); git__free(t->host); git__free(t->port); @@ -669,13 +579,15 @@ int git_transport_http(git_transport **out) memset(t, 0x0, sizeof(transport_http)); t->parent.connect = http_connect; - t->parent.ls = http_ls; - t->parent.negotiate_fetch = http_negotiate_fetch; - t->parent.download_pack = http_download_pack; + t->parent.negotiation_step = http_negotiation_step; t->parent.close = http_close; t->parent.free = http_free; - t->proto.refs = &t->refs; - t->proto.transport = (git_transport *) t; + 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 @@ -693,12 +605,12 @@ int git_transport_http(git_transport **out) int git_transport_https(git_transport **out) { -#ifdef GIT_SSL +#if defined(GIT_SSL) || defined(GIT_WINHTTP) transport_http *t; if (git_transport_http((git_transport **)&t) < 0) return -1; - t->parent.encrypt = 1; + t->parent.use_ssl = 1; t->parent.check_cert = 1; *out = (git_transport *) t; diff --git a/src/transports/local.c b/src/transports/local.c index 0e1ae3752ac..561c84fa05f 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -15,11 +15,11 @@ #include "posix.h" #include "path.h" #include "buffer.h" +#include "pkt.h" typedef struct { git_transport parent; git_repository *repo; - git_vector refs; } transport_local; static int add_ref(transport_local *t, const char *name) @@ -27,19 +27,32 @@ 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)); 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_vector_insert(&t->refs, head) < 0) - { - git__free(head->name); + if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) { git__free(head); + git__free(pkt->head.name); + git__free(pkt); + } + + pkt->type = GIT_PKT_REF; + memcpy(&pkt->head, head, sizeof(git_remote_head)); + git__free(head); + + if (git_vector_insert(&transport->refs, pkt) < 0) + { + git__free(pkt->head.name); + git__free(pkt); return -1; } @@ -47,7 +60,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, &head->oid, GIT_OBJ_ANY) < 0) + if (git_object_lookup(&obj, t->repo, &pkt->head.oid, GIT_OBJ_ANY) < 0) return -1; head = NULL; @@ -66,14 +79,20 @@ static int add_ref(transport_local *t, const char *name) 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(&t->refs, head) < 0) + if (git_vector_insert(&transport->refs, pkt) < 0) return -1; return 0; @@ -88,11 +107,12 @@ 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(&t->refs, (unsigned int)ref_names.count, NULL) < 0) + git_vector_init(&transport->refs, (unsigned int)ref_names.count, NULL) < 0) goto on_error; /* Sort the references first */ @@ -111,28 +131,11 @@ static int store_refs(transport_local *t) return 0; on_error: - git_vector_free(&t->refs); + git_vector_free(&transport->refs); git_strarray_free(&ref_names); return -1; } -static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload) -{ - transport_local *t = (transport_local *) transport; - git_vector *refs = &t->refs; - unsigned int i; - git_remote_head *h; - - assert(transport && transport->connected); - - git_vector_foreach(refs, i, h) { - if (list_cb(h, payload) < 0) - return -1; - } - - return 0; -} - /* * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calulating the heads ourselves. @@ -201,14 +204,14 @@ static void local_free(git_transport *transport) { unsigned int i; transport_local *t = (transport_local *) transport; - git_vector *vec = &t->refs; - git_remote_head *h; + git_vector *vec = &transport->refs; + git_pkt_ref *pkt; assert(transport); - git_vector_foreach (vec, i, h) { - git__free(h->name); - git__free(h); + git_vector_foreach (vec, i, pkt) { + git__free(pkt->head.name); + git__free(pkt); } git_vector_free(vec); @@ -229,8 +232,8 @@ int git_transport_local(git_transport **out) memset(t, 0x0, sizeof(transport_local)); + t->parent.own_logic = 1; t->parent.connect = local_connect; - t->parent.ls = local_ls; t->parent.negotiate_fetch = local_negotiate_fetch; t->parent.close = local_close; t->parent.free = local_free; diff --git a/src/tree.c b/src/tree.c index b609eea33e7..83aa303d444 100644 --- a/src/tree.c +++ b/src/tree.c @@ -12,12 +12,16 @@ #include "git2/object.h" #define DEFAULT_TREE_SIZE 16 -#define MAX_FILEMODE 0777777 #define MAX_FILEMODE_BYTES 6 -static int valid_attributes(const int attributes) +static bool valid_filemode(const int filemode) { - return attributes >= 0 && attributes <= MAX_FILEMODE; + return (filemode == GIT_FILEMODE_TREE + || filemode == GIT_FILEMODE_BLOB + || filemode == GIT_FILEMODE_BLOB_GROUP_WRITABLE + || filemode == GIT_FILEMODE_BLOB_EXECUTABLE + || filemode == GIT_FILEMODE_LINK + || filemode == GIT_FILEMODE_COMMIT); } static int valid_entry_name(const char *filename) @@ -140,6 +144,9 @@ static int tree_key_search(git_vector *entries, const char *filename, size_t fil void git_tree_entry_free(git_tree_entry *entry) { + if (entry == NULL) + return; + git__free(entry); } @@ -178,9 +185,9 @@ const git_oid *git_tree_id(git_tree *c) return git_object_id((git_object *)c); } -unsigned int git_tree_entry_attributes(const git_tree_entry *entry) +git_filemode_t git_tree_entry_filemode(const git_tree_entry *entry) { - return entry->attr; + return (git_filemode_t)entry->attr; } const char *git_tree_entry_name(const git_tree_entry *entry) @@ -231,12 +238,27 @@ const git_tree_entry *git_tree_entry_byname(git_tree *tree, const char *filename return entry_fromname(tree, filename, strlen(filename)); } -const git_tree_entry *git_tree_entry_byindex(git_tree *tree, unsigned int idx) +const git_tree_entry *git_tree_entry_byindex(git_tree *tree, size_t idx) { assert(tree); return git_vector_get(&tree->entries, idx); } +const git_tree_entry *git_tree_entry_byoid(git_tree *tree, const git_oid *oid) +{ + unsigned int i; + git_tree_entry *e; + + assert(tree); + + git_vector_foreach(&tree->entries, i, e) { + if (memcmp(&e->oid.id, &oid->id, sizeof(oid->id)) == 0) + return e; + } + + return NULL; +} + int git_tree__prefix_position(git_tree *tree, const char *path) { git_vector *entries = &tree->entries; @@ -267,7 +289,7 @@ int git_tree__prefix_position(git_tree *tree, const char *path) unsigned int git_tree_entrycount(git_tree *tree) { assert(tree); - return tree->entries.length; + return (unsigned int)tree->entries.length; } static int tree_error(const char *str) @@ -286,8 +308,8 @@ static int tree_parse_buffer(git_tree *tree, const char *buffer, const char *buf int attr; if (git__strtol32(&attr, buffer, &buffer, 8) < 0 || - !buffer || !valid_attributes(attr)) - return tree_error("Failed to parse tree. Can't parse attributes"); + !buffer || !valid_filemode(attr)) + return tree_error("Failed to parse tree. Can't parse filemode"); if (*buffer++ != ' ') return tree_error("Failed to parse tree. Object is corrupted"); @@ -346,7 +368,7 @@ static int append_entry( git_treebuilder *bld, const char *filename, const git_oid *id, - unsigned int attributes) + git_filemode_t filemode) { git_tree_entry *entry; @@ -354,7 +376,7 @@ static int append_entry( GITERR_CHECK_ALLOC(entry); git_oid_cpy(&entry->oid, id); - entry->attr = attributes; + entry->attr = (uint16_t)filemode; if (git_vector_insert(&bld->entries, entry) < 0) return -1; @@ -495,10 +517,23 @@ static void sort_entries(git_treebuilder *bld) git_vector_sort(&bld->entries); } +GIT_INLINE(git_filemode_t) normalize_filemode(git_filemode_t filemode) +{ + /* 100664 mode is an early design mistake. Tree entries may bear + * this mode in some old git repositories, but it's now deprecated. + * We silently normalize while inserting new entries in a tree + * being built. + */ + if (filemode == GIT_FILEMODE_BLOB_GROUP_WRITABLE) + return GIT_FILEMODE_BLOB; + + return filemode; +} + int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) { git_treebuilder *bld; - unsigned int i, source_entries = DEFAULT_TREE_SIZE; + size_t i, source_entries = DEFAULT_TREE_SIZE; assert(builder_p); @@ -515,7 +550,10 @@ int git_treebuilder_create(git_treebuilder **builder_p, const git_tree *source) for (i = 0; i < source->entries.length; ++i) { git_tree_entry *entry_src = source->entries.contents[i]; - if (append_entry(bld, entry_src->filename, &entry_src->oid, entry_src->attr) < 0) + if (append_entry( + bld, entry_src->filename, + &entry_src->oid, + normalize_filemode((git_filemode_t)entry_src->attr)) < 0) goto on_error; } } @@ -533,15 +571,17 @@ int git_treebuilder_insert( git_treebuilder *bld, const char *filename, const git_oid *id, - unsigned int attributes) + git_filemode_t filemode) { git_tree_entry *entry; int pos; assert(bld && id && filename); - if (!valid_attributes(attributes)) - return tree_error("Failed to insert entry. Invalid attributes"); + if (!valid_filemode(filemode)) + return tree_error("Failed to insert entry. Invalid filemode"); + + filemode = normalize_filemode(filemode); if (!valid_entry_name(filename)) return tree_error("Failed to insert entry. Invalid name for a tree entry"); @@ -558,7 +598,7 @@ int git_treebuilder_insert( } git_oid_cpy(&entry->oid, id); - entry->attr = attributes; + entry->attr = filemode; if (pos < 0) { if (git_vector_insert(&bld->entries, entry) < 0) @@ -721,13 +761,13 @@ int git_tree_entry_bypath( } switch (path[filename_len]) { - case '/': + case '/': /* If there are more components in the path... * then this entry *must* be a tree */ if (!git_tree_entry__is_tree(entry)) { giterr_set(GITERR_TREE, "The path '%s' does not exist in the given tree", path); - return -1; + return GIT_ENOTFOUND; } /* If there's only a slash left in the path, we @@ -756,11 +796,12 @@ int git_tree_entry_bypath( return error; } -static int tree_walk_post( +static int tree_walk( git_tree *tree, git_treewalk_cb callback, git_buf *path, - void *payload) + void *payload, + bool preorder) { int error = 0; unsigned int i; @@ -768,8 +809,13 @@ static int tree_walk_post( for (i = 0; i < tree->entries.length; ++i) { git_tree_entry *entry = tree->entries.contents[i]; - if (callback(path->ptr, entry, payload) < 0) - continue; + if (preorder) { + error = callback(path->ptr, entry, payload); + if (error > 0) + continue; + if (error < 0) + return GIT_EUSER; + } if (git_tree_entry__is_tree(entry)) { git_tree *subtree; @@ -786,15 +832,21 @@ static int tree_walk_post( if (git_buf_oom(path)) return -1; - if (tree_walk_post(subtree, callback, path, payload) < 0) - return -1; + error = tree_walk(subtree, callback, path, payload, preorder); + if (error != 0) + break; git_buf_truncate(path, path_len); git_tree_free(subtree); } + + if (!preorder && callback(path->ptr, entry, payload) < 0) { + error = GIT_EUSER; + break; + } } - return 0; + return error; } int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payload) @@ -804,12 +856,12 @@ int git_tree_walk(git_tree *tree, git_treewalk_cb callback, int mode, void *payl switch (mode) { case GIT_TREEWALK_POST: - error = tree_walk_post(tree, callback, &root_path, payload); + error = tree_walk(tree, callback, &root_path, payload, false); break; case GIT_TREEWALK_PRE: - tree_error("Preorder tree walking is still not implemented"); - return -1; + error = tree_walk(tree, callback, &root_path, payload, true); + break; default: giterr_set(GITERR_INVALID, "Invalid walking mode for tree walk"); diff --git a/src/tree.h b/src/tree.h index c49309cbc62..24b517ce37e 100644 --- a/src/tree.h +++ b/src/tree.h @@ -47,5 +47,9 @@ int git_tree__parse(git_tree *tree, git_odb_object *obj); */ int git_tree__prefix_position(git_tree *tree, const char *prefix); +/** + * Obsolete mode kept for compatibility reasons + */ +#define GIT_FILEMODE_BLOB_GROUP_WRITABLE 0100664 #endif diff --git a/src/unix/map.c b/src/unix/map.c index 9dcae5845c8..ee7888c1754 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -31,6 +31,8 @@ int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offs mflag = MAP_SHARED; else if ((flags & GIT_MAP_TYPE) == GIT_MAP_PRIVATE) mflag = MAP_PRIVATE; + else + mflag = MAP_SHARED; out->data = mmap(NULL, len, mprot, mflag, fd, offset); diff --git a/src/unix/posix.h b/src/unix/posix.h index 83fd8a18966..25038c82736 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -18,6 +18,7 @@ #define p_lstat(p,b) lstat(p,b) #define p_readlink(a, b, c) readlink(a, b, c) +#define p_symlink(o,n) symlink(o, n) #define p_link(o,n) link(o, n) #define p_unlink(p) unlink(p) #define p_mkdir(p,m) mkdir(p, m) diff --git a/src/util.c b/src/util.c index 3093cd7676c..719714105fc 100644 --- a/src/util.c +++ b/src/util.c @@ -22,6 +22,18 @@ void git_libgit2_version(int *major, int *minor, int *rev) *rev = LIBGIT2_VER_REVISION; } +int git_libgit2_capabilities() +{ + return 0 +#ifdef GIT_THREADS + | GIT_CAP_THREADS +#endif +#if defined(GIT_SSL) || defined(GIT_WINHTTP) + | GIT_CAP_HTTPS +#endif + ; +} + void git_strarray_free(git_strarray *array) { size_t i; @@ -435,3 +447,21 @@ int git__parse_bool(int *out, const char *value) return -1; } + +size_t git__unescape(char *str) +{ + char *scan, *pos = str; + + for (scan = str; *scan; pos++, scan++) { + if (*scan == '\\' && *(scan + 1) != '\0') + scan++; /* skip '\' but include next char */ + if (pos != scan) + *pos = *scan; + } + + if (pos != scan) { + *pos = '\0'; + } + + return (pos - str); +} diff --git a/src/util.h b/src/util.h index eed2bc80c28..905fc927ff6 100644 --- a/src/util.h +++ b/src/util.h @@ -204,16 +204,14 @@ GIT_INLINE(bool) git__isalpha(int c) return ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')); } -GIT_INLINE(bool) git__isspace(int c) +GIT_INLINE(bool) git__isdigit(int c) { - return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v'); + return (c >= '0' && c <= '9'); } -GIT_INLINE(int) git__time_cmp(const git_time *a, const git_time *b) +GIT_INLINE(bool) git__isspace(int c) { - /* Adjust for time zones. Times are in seconds, offsets are in minutes. */ - git_time_t adjusted_a = a->time + ((b->offset - a->offset) * 60); - return (int)(adjusted_a - b->time); + return (c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == '\v' || c == 0x85 /* Unicode CR+LF */); } GIT_INLINE(bool) git__iswildcard(int c) @@ -240,4 +238,13 @@ extern int git__parse_bool(int *out, const char *value); */ int git__date_parse(git_time_t *out, const char *date); +/* + * Unescapes a string in-place. + * + * Edge cases behavior: + * - "jackie\" -> "jacky\" + * - "chan\\" -> "chan\" + */ +extern size_t git__unescape(char *str); + #endif /* INCLUDE_util_h__ */ diff --git a/src/vector.c b/src/vector.c index 6f9aacccf6e..0308ce26eaa 100644 --- a/src/vector.c +++ b/src/vector.c @@ -35,7 +35,7 @@ void git_vector_free(git_vector *v) v->_alloc_size = 0; } -int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp) +int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp) { assert(v); diff --git a/src/vector.h b/src/vector.h index 9139db3457e..f75e634ba6b 100644 --- a/src/vector.h +++ b/src/vector.h @@ -12,16 +12,16 @@ typedef int (*git_vector_cmp)(const void *, const void *); typedef struct git_vector { - unsigned int _alloc_size; + size_t _alloc_size; git_vector_cmp _cmp; void **contents; - unsigned int length; + size_t length; int sorted; } git_vector; #define GIT_VECTOR_INIT {0} -int git_vector_init(git_vector *v, unsigned int initial_size, git_vector_cmp cmp); +int git_vector_init(git_vector *v, size_t initial_size, git_vector_cmp cmp); void git_vector_free(git_vector *v); void git_vector_clear(git_vector *v); void git_vector_swap(git_vector *a, git_vector *b); @@ -45,12 +45,12 @@ GIT_INLINE(int) git_vector_bsearch2( return git_vector_bsearch3(NULL, v, cmp, key); } -GIT_INLINE(void *) git_vector_get(git_vector *v, unsigned int position) +GIT_INLINE(void *) git_vector_get(git_vector *v, size_t position) { return (position < v->length) ? v->contents[position] : NULL; } -GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, unsigned int position) +GIT_INLINE(const void *) git_vector_get_const(const git_vector *v, size_t position) { return (position < v->length) ? v->contents[position] : NULL; } diff --git a/src/win32/dir.c b/src/win32/dir.c index bc3d40fa585..5cb1082bc95 100644 --- a/src/win32/dir.c +++ b/src/win32/dir.c @@ -7,7 +7,6 @@ #define GIT__WIN32_NO_WRAP_DIR #include "dir.h" #include "utf-conv.h" -#include "git2/windows.h" static int init_filter(char *filter, size_t n, const char *dir) { @@ -26,8 +25,8 @@ static int init_filter(char *filter, size_t n, const char *dir) git__DIR *git__opendir(const char *dir) { - char filter[4096]; - wchar_t* filter_w = NULL; + char filter[GIT_WIN_PATH]; + wchar_t filter_w[GIT_WIN_PATH]; git__DIR *new = NULL; if (!dir || !init_filter(filter, sizeof(filter), dir)) @@ -41,12 +40,8 @@ git__DIR *git__opendir(const char *dir) if (!new->dir) goto fail; - filter_w = gitwin_to_utf16(filter); - if (!filter_w) - goto fail; - + git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); new->h = FindFirstFileW(filter_w, &new->f); - git__free(filter_w); if (new->h == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Could not open directory '%s'", dir); @@ -85,16 +80,9 @@ int git__readdir_ext( if (wcslen(d->f.cFileName) >= sizeof(entry->d_name)) return -1; + git__utf16_to_8(entry->d_name, d->f.cFileName); entry->d_ino = 0; - if (WideCharToMultiByte( - gitwin_get_codepage(), 0, d->f.cFileName, -1, - entry->d_name, GIT_PATH_MAX, NULL, NULL) == 0) - { - giterr_set(GITERR_OS, "Could not convert filename to UTF-8"); - return -1; - } - *result = entry; if (is_dir != NULL) @@ -113,8 +101,8 @@ struct git__dirent *git__readdir(git__DIR *d) void git__rewinddir(git__DIR *d) { - char filter[4096]; - wchar_t* filter_w; + char filter[GIT_WIN_PATH]; + wchar_t filter_w[GIT_WIN_PATH]; if (!d) return; @@ -125,12 +113,11 @@ void git__rewinddir(git__DIR *d) d->first = 0; } - if (!init_filter(filter, sizeof(filter), d->dir) || - (filter_w = gitwin_to_utf16(filter)) == NULL) + if (!init_filter(filter, sizeof(filter), d->dir)) return; + git__utf8_to_16(filter_w, GIT_WIN_PATH, filter); d->h = FindFirstFileW(filter_w, &d->f); - git__free(filter_w); if (d->h == INVALID_HANDLE_VALUE) giterr_set(GITERR_OS, "Could not open directory '%s'", d->dir); diff --git a/src/win32/posix.h b/src/win32/posix.h index baa4a3b4ea3..da46cf514ed 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -21,18 +21,16 @@ GIT_INLINE(int) p_link(const char *old, const char *new) GIT_INLINE(int) p_mkdir(const char *path, mode_t mode) { - wchar_t* buf = gitwin_to_utf16(path); - int ret = _wmkdir(buf); - + wchar_t buf[GIT_WIN_PATH]; GIT_UNUSED(mode); - - git__free(buf); - return ret; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _wmkdir(buf); } extern int p_unlink(const char *path); extern int p_lstat(const char *file_name, struct stat *buf); extern int p_readlink(const char *link, char *target, size_t target_len); +extern int p_symlink(const char *old, const char *new); extern int p_hide_directory__w32(const char *path); extern char *p_realpath(const char *orig_path, char *buffer); extern int p_vsnprintf(char *buffer, size_t count, const char *format, va_list argptr); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 4e0150fb5df..649fe9b95b0 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -7,6 +7,7 @@ #include "../posix.h" #include "path.h" #include "utf-conv.h" +#include "repository.h" #include #include #include @@ -14,16 +15,10 @@ int p_unlink(const char *path) { - int ret = 0; - wchar_t* buf; - - if ((buf = gitwin_to_utf16(path)) != NULL) { - _wchmod(buf, 0666); - ret = _wunlink(buf); - git__free(buf); - } - - return ret; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + _wchmod(buf, 0666); + return _wunlink(buf); } int p_fsync(int fd) @@ -60,9 +55,10 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) static int do_lstat(const char *file_name, struct stat *buf) { WIN32_FILE_ATTRIBUTE_DATA fdata; - wchar_t* fbuf = gitwin_to_utf16(file_name); - if (!fbuf) - return -1; + wchar_t fbuf[GIT_WIN_PATH]; + DWORD last_error; + + git__utf8_to_16(fbuf, GIT_WIN_PATH, file_name); if (GetFileAttributesExW(fbuf, GetFileExInfoStandard, &fdata)) { int fMode = S_IREAD; @@ -88,12 +84,15 @@ 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)); - - git__free(fbuf); return 0; } - git__free(fbuf); + last_error = GetLastError(); + if (last_error == ERROR_FILE_NOT_FOUND) + errno = ENOENT; + else if (last_error == ERROR_PATH_NOT_FOUND) + errno = ENOTDIR; + return -1; } @@ -135,7 +134,7 @@ int p_readlink(const char *link, char *target, size_t target_len) static fpath_func pGetFinalPath = NULL; HANDLE hFile; DWORD dwRet; - wchar_t* link_w; + wchar_t link_w[GIT_WIN_PATH]; wchar_t* target_w; int error = 0; @@ -158,8 +157,7 @@ int p_readlink(const char *link, char *target, size_t target_len) } } - link_w = gitwin_to_utf16(link); - GITERR_CHECK_ALLOC(link_w); + git__utf8_to_16(link_w, GIT_WIN_PATH, link); hFile = CreateFileW(link_w, // file to open GENERIC_READ, // open for reading @@ -169,8 +167,6 @@ int p_readlink(const char *link, char *target, size_t target_len) FILE_FLAG_BACKUP_SEMANTICS, // normal file NULL); // no attr. template - git__free(link_w); - if (hFile == INVALID_HANDLE_VALUE) { giterr_set(GITERR_OS, "Cannot open '%s' for reading", link); return -1; @@ -217,18 +213,22 @@ int p_readlink(const char *link, char *target, size_t target_len) return dwRet; } +int p_symlink(const char *old, const char *new) +{ + /* Real symlinks on NTFS require admin privileges. Until this changes, + * libgit2 just creates a text file with the link target in the contents. + */ + return git_futils_fake_symlink(old, new); +} + int p_open(const char *path, int flags, ...) { - int fd; - wchar_t* buf; + wchar_t buf[GIT_WIN_PATH]; mode_t mode = 0; - buf = gitwin_to_utf16(path); - if (!buf) - return -1; + git__utf8_to_16(buf, GIT_WIN_PATH, path); - if (flags & O_CREAT) - { + if (flags & O_CREAT) { va_list arg_list; va_start(arg_list, flags); @@ -236,27 +236,20 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - fd = _wopen(buf, flags | _O_BINARY, mode); - - git__free(buf); - return fd; + return _wopen(buf, flags | _O_BINARY, mode); } int p_creat(const char *path, mode_t mode) { - int fd; - wchar_t* buf = gitwin_to_utf16(path); - if (!buf) - return -1; - fd = _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); - git__free(buf); - return fd; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); } int p_getcwd(char *buffer_out, size_t size) { int ret; - wchar_t* buf; + wchar_t *buf; if ((size_t)((int)size) != size) return -1; @@ -280,64 +273,43 @@ int p_stat(const char* path, struct stat* buf) int p_chdir(const char* path) { - wchar_t* buf = gitwin_to_utf16(path); - int ret; - if (!buf) - return -1; - ret = _wchdir(buf); - git__free(buf); - return ret; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _wchdir(buf); } int p_chmod(const char* path, mode_t mode) { - wchar_t* buf = gitwin_to_utf16(path); - int ret; - if (!buf) - return -1; - ret = _wchmod(buf, mode); - git__free(buf); - return ret; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _wchmod(buf, mode); } int p_rmdir(const char* path) { - wchar_t* buf = gitwin_to_utf16(path); - int ret; - if (!buf) - return -1; - ret = _wrmdir(buf); - git__free(buf); - return ret; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _wrmdir(buf); } int p_hide_directory__w32(const char *path) { - int res; - wchar_t* buf = gitwin_to_utf16(path); - if (!buf) - return -1; - - res = SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN); - git__free(buf); - - return (res != 0) ? 0 : -1; /* MSDN states a "non zero" value indicates a success */ + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return (SetFileAttributesW(buf, FILE_ATTRIBUTE_HIDDEN) != 0) ? 0 : -1; } char *p_realpath(const char *orig_path, char *buffer) { int ret, buffer_sz = 0; - wchar_t* orig_path_w = gitwin_to_utf16(orig_path); - wchar_t* buffer_w = (wchar_t*)git__malloc(GIT_PATH_MAX * sizeof(wchar_t)); + wchar_t orig_path_w[GIT_WIN_PATH]; + wchar_t buffer_w[GIT_WIN_PATH]; - if (!orig_path_w || !buffer_w) - return NULL; - - ret = GetFullPathNameW(orig_path_w, GIT_PATH_MAX, buffer_w, NULL); - git__free(orig_path_w); + git__utf8_to_16(orig_path_w, GIT_WIN_PATH, orig_path); + 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_PATH_MAX) { + if (ret == 0 || ret > GIT_WIN_PATH) { buffer = NULL; goto done; } @@ -360,8 +332,7 @@ char *p_realpath(const char *orig_path, char *buffer) } } - if (!git_path_exists(buffer)) - { + if (!git_path_exists(buffer)) { if (buffer_sz > 0) git__free(buffer); @@ -370,9 +341,9 @@ char *p_realpath(const char *orig_path, char *buffer) } done: - git__free(buffer_w); if (buffer) git_path_mkposix(buffer); + return buffer; } @@ -427,32 +398,19 @@ int p_setenv(const char* name, const char* value, int overwrite) int p_access(const char* path, mode_t mode) { - wchar_t *buf = gitwin_to_utf16(path); - int ret; - if (!buf) - return -1; - - ret = _waccess(buf, mode); - git__free(buf); - - return ret; + wchar_t buf[GIT_WIN_PATH]; + git__utf8_to_16(buf, GIT_WIN_PATH, path); + return _waccess(buf, mode); } int p_rename(const char *from, const char *to) { - wchar_t *wfrom = gitwin_to_utf16(from); - wchar_t *wto = gitwin_to_utf16(to); - int ret; - - if (!wfrom || !wto) - return -1; - - ret = MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; - - git__free(wfrom); - git__free(wto); + wchar_t wfrom[GIT_WIN_PATH]; + wchar_t wto[GIT_WIN_PATH]; - return ret; + git__utf8_to_16(wfrom, GIT_WIN_PATH, from); + git__utf8_to_16(wto, GIT_WIN_PATH, to); + return MoveFileExW(wfrom, wto, MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED) ? 0 : -1; } int p_recv(GIT_SOCKET socket, void *buffer, size_t length, int flags) diff --git a/src/win32/utf-conv.c b/src/win32/utf-conv.c index 0a705c0ad54..396af7cadc2 100644 --- a/src/win32/utf-conv.c +++ b/src/win32/utf-conv.c @@ -7,86 +7,75 @@ #include "common.h" #include "utf-conv.h" -#include "git2/windows.h" -/* - * Default codepage value - */ -static int _active_codepage = CP_UTF8; - -void gitwin_set_codepage(unsigned int codepage) -{ - _active_codepage = codepage; -} - -unsigned int gitwin_get_codepage(void) -{ - return _active_codepage; -} - -void gitwin_set_utf8(void) -{ - _active_codepage = CP_UTF8; -} +#define U16_LEAD(c) (wchar_t)(((c)>>10)+0xd7c0) +#define U16_TRAIL(c) (wchar_t)(((c)&0x3ff)|0xdc00) -wchar_t* gitwin_to_utf16(const char* str) +#if 0 +void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) { - wchar_t* ret; - int cb; - - if (!str) - return NULL; - - cb = MultiByteToWideChar(_active_codepage, 0, str, -1, NULL, 0); - if (cb == 0) - return (wchar_t *)git__calloc(1, sizeof(wchar_t)); - - ret = (wchar_t *)git__malloc(cb * sizeof(wchar_t)); - if (!ret) - return NULL; - - if (MultiByteToWideChar(_active_codepage, 0, str, -1, ret, (int)cb) == 0) { - giterr_set(GITERR_OS, "Could not convert string to UTF-16"); - git__free(ret); - ret = NULL; + wchar_t *pDest = dest; + uint32_t ch; + const uint8_t* pSrc = (uint8_t*) src; + + assert(dest && src && length); + + length--; + + while(*pSrc && length > 0) { + ch = *pSrc++; + length--; + + if(ch < 0xc0) { + /* + * ASCII, or a trail byte in lead position which is treated like + * a single-byte sequence for better character boundary + * resynchronization after illegal sequences. + */ + *pDest++ = (wchar_t)ch; + continue; + } else if(ch < 0xe0) { /* U+0080..U+07FF */ + if (pSrc[0]) { + /* 0x3080 = (0xc0 << 6) + 0x80 */ + *pDest++ = (wchar_t)((ch << 6) + *pSrc++ - 0x3080); + continue; + } + } else if(ch < 0xf0) { /* U+0800..U+FFFF */ + if (pSrc[0] && pSrc[1]) { + /* no need for (ch & 0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */ + /* 0x2080 = (0x80 << 6) + 0x80 */ + ch = (ch << 12) + (*pSrc++ << 6); + *pDest++ = (wchar_t)(ch + *pSrc++ - 0x2080); + continue; + } + } else /* f0..f4 */ { /* U+10000..U+10FFFF */ + if (length >= 1 && pSrc[0] && pSrc[1] && pSrc[2]) { + /* 0x3c82080 = (0xf0 << 18) + (0x80 << 12) + (0x80 << 6) + 0x80 */ + ch = (ch << 18) + (*pSrc++ << 12); + ch += *pSrc++ << 6; + ch += *pSrc++ - 0x3c82080; + *(pDest++) = U16_LEAD(ch); + *(pDest++) = U16_TRAIL(ch); + length--; /* two bytes for this character */ + continue; + } + } + + /* truncated character at the end */ + *pDest++ = 0xfffd; + break; } - return ret; + *pDest++ = 0x0; } +#endif -int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len) +void git__utf8_to_16(wchar_t *dest, size_t length, const char *src) { - int result = MultiByteToWideChar( - _active_codepage, 0, str, -1, buffer, (int)len); - if (result == 0) - giterr_set(GITERR_OS, "Could not convert string to UTF-16"); - return result; + MultiByteToWideChar(CP_UTF8, 0, src, -1, dest, (int)length); } -char* gitwin_from_utf16(const wchar_t* str) +void git__utf16_to_8(char *out, const wchar_t *input) { - char* ret; - int cb; - - if (!str) - return NULL; - - cb = WideCharToMultiByte(_active_codepage, 0, str, -1, NULL, 0, NULL, NULL); - if (cb == 0) - return (char *)git__calloc(1, sizeof(char)); - - ret = (char*)git__malloc(cb); - if (!ret) - return NULL; - - if (WideCharToMultiByte( - _active_codepage, 0, str, -1, ret, (int)cb, NULL, NULL) == 0) - { - giterr_set(GITERR_OS, "Could not convert string to UTF-8"); - git__free(ret); - ret = NULL; - } - - return ret; - + 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 ae9f29f6c98..3bd1549bce5 100644 --- a/src/win32/utf-conv.h +++ b/src/win32/utf-conv.h @@ -10,9 +10,10 @@ #ifndef INCLUDE_git_utfconv_h__ #define INCLUDE_git_utfconv_h__ -wchar_t* gitwin_to_utf16(const char* str); -int gitwin_append_utf16(wchar_t *buffer, const char *str, size_t len); -char* gitwin_from_utf16(const wchar_t* str); +#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); #endif diff --git a/tests-clar/attr/repo.c b/tests-clar/attr/repo.c index c37ff544afa..4a317e4f3ed 100644 --- a/tests-clar/attr/repo.c +++ b/tests-clar/attr/repo.c @@ -113,6 +113,22 @@ static int count_attrs( return 0; } +static int cancel_iteration( + const char *name, + const char *value, + void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(value); + + *((int *)payload) -= 1; + + if (*((int *)payload) < 0) + return -1; + + return 0; +} + void test_attr_repo__foreach(void) { int count; @@ -131,6 +147,12 @@ void test_attr_repo__foreach(void) cl_git_pass(git_attr_foreach(g_repo, 0, "sub/subdir_test2.txt", &count_attrs, &count)); cl_assert(count == 6); /* repoattr, rootattr, subattr, reposub, negattr, another */ + + count = 2; + cl_assert_equal_i( + GIT_EUSER, git_attr_foreach( + g_repo, 0, "sub/subdir_test1", &cancel_iteration, &count) + ); } void test_attr_repo__manpage_example(void) diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c new file mode 100644 index 00000000000..f017a0fe22b --- /dev/null +++ b/tests-clar/checkout/index.c @@ -0,0 +1,362 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + +static git_repository *g_repo; +static git_checkout_opts g_opts; + +static void reset_index_to_treeish(git_object *treeish) +{ + git_object *tree; + git_index *index; + + 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_write(index)); + + git_object_free(tree); + git_index_free(index); +} + +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_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_repository_head_tree(&tree, g_repo)); + + reset_index_to_treeish((git_object *)tree); + git_tree_free(tree); + + cl_git_rewritefile( + "./testrepo/.gitattributes", + "* text eol=lf\n"); +} + +void test_checkout_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static void test_file_contents(const char *path, const char *expectedcontents) +{ + int fd; + char buffer[1024] = {0}; + size_t expectedlen, actuallen; + + fd = p_open(path, O_RDONLY); + cl_assert(fd >= 0); + + expectedlen = strlen(expectedcontents); + actuallen = p_read(fd, buffer, 1024); + cl_git_pass(p_close(fd)); + + cl_assert_equal_sz(actuallen, expectedlen); + cl_assert_equal_s(buffer, expectedcontents); +} + +void test_checkout_index__cannot_checkout_a_bare_repository(void) +{ + test_checkout_index__cleanup(); + + memset(&g_opts, 0, sizeof(g_opts)); + g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_fail(git_checkout_index(g_repo, NULL, NULL)); +} + +void test_checkout_index__can_create_missing_files(void) +{ + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + 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)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__can_remove_untracked_files(void) +{ + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + cl_git_mkfile("./testrepo/dir/one", "one\n"); + cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); + + 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)); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/dir")); +} + +void test_checkout_index__honor_the_specified_pathspecs(void) +{ + char *entries[] = { "*.txt" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); + 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_assert_equal_i(false, git_path_isfile("./testrepo/README")); + test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +static void set_config_entry_to(const char *entry_name, bool value) +{ + git_config *cfg; + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_set_bool(cfg, entry_name, value)); + + git_config_free(cfg); +} + +static void set_core_autocrlf_to(bool value) +{ + set_config_entry_to("core.autocrlf", value); +} + +void test_checkout_index__honor_the_gitattributes_directives(void) +{ + const char *attributes = + "branch_file.txt text eol=crlf\n" + "new.txt text eol=lf\n"; + + cl_git_mkfile("./testrepo/.gitattributes", attributes); + set_core_autocrlf_to(false); + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/README", "hey there\n"); + test_file_contents("./testrepo/new.txt", "my new file\n"); + test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); +} + +void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) +{ +#ifdef GIT_WIN32 + const char *expected_readme_text = "hey there\r\n"; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + set_core_autocrlf_to(true); + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + test_file_contents("./testrepo/README", expected_readme_text); +#endif +} + +static void set_repo_symlink_handling_cap_to(bool value) +{ + set_config_entry_to("core.symlinks", value); +} + +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)); + +#ifdef GIT_WIN32 + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +#else + { + char link_data[1024]; + size_t link_size = 1024; + + link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size); + link_data[link_size] = '\0'; + cl_assert_equal_i(link_size, strlen("new.txt")); + cl_assert_equal_s(link_data, "new.txt"); + test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + } +#endif +} + +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)); + + test_file_contents("./testrepo/link_to_new.txt", "new.txt"); +} + +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)); + + test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); +} + +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)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +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)); + + 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)); + + test_file_contents("./testrepo/new.txt", "my new file\n"); +} + +void test_checkout_index__options_dir_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + git_oid oid; + git_commit *commit; + + cl_git_pass(git_reference_name_to_oid(&oid, g_repo, "refs/heads/dir")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &oid)); + + 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(p_stat("./testrepo/a", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0701); + + /* File-mode test, since we're on the 'dir' branch */ + cl_git_pass(p_stat("./testrepo/a/b.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0755); + + git_commit_free(commit); +#endif +} + +void test_checkout_index__options_override_file_modes(void) +{ +#ifndef GIT_WIN32 + struct stat st; + + g_opts.file_mode = 0700; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); + + cl_git_pass(p_stat("./testrepo/new.txt", &st)); + cl_assert_equal_i(st.st_mode & 0777, 0700); +#endif +} + +void test_checkout_index__options_open_flags(void) +{ + cl_git_mkfile("./testrepo/new.txt", "hi\n"); + + 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)); + + test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); +} + +struct notify_data { + const char *file; + const char *sha; +}; + +static int notify_cb( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload) +{ + struct notify_data *expectations = (struct notify_data *)payload; + + GIT_UNUSED(file_mode); + + cl_assert_equal_s(expectations->file, skipped_file); + cl_assert_equal_i(0, git_oid_streq(blob_oid, expectations->sha)); + + return 0; +} + +void test_checkout_index__can_notify_of_skipped_files(void) +{ + struct notify_data data; + + cl_git_mkfile("./testrepo/new.txt", "This isn't what's stored!"); + + /* + * $ git ls-tree HEAD + * 100644 blob a8233120f6ad708f843d861ce2b7228ec4e3dec6 README + * 100644 blob 3697d64be941a53d4ae8f6a271e4e3fa56b022cc branch_file.txt + * 100644 blob a71586c1dfe8a71c6cbf6c129f404c5642ff31bd new.txt + */ + 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; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); +} + +static int dont_notify_cb( + const char *skipped_file, + const git_oid *blob_oid, + int file_mode, + void *payload) +{ + GIT_UNUSED(skipped_file); + GIT_UNUSED(blob_oid); + GIT_UNUSED(file_mode); + GIT_UNUSED(payload); + + cl_assert(false); + + return 0; +} + +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; + + cl_git_pass(git_checkout_index(g_repo, &g_opts, NULL)); +} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c new file mode 100644 index 00000000000..6d573bfd7b1 --- /dev/null +++ b/tests-clar/checkout/tree.c @@ -0,0 +1,65 @@ +#include "clar_libgit2.h" + +#include "git2/checkout.h" +#include "repository.h" + +static git_repository *g_repo; +static git_checkout_opts g_opts; +static git_object *g_object; + +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; +} + +void test_checkout_tree__cleanup(void) +{ + git_object_free(g_object); + + cl_git_sandbox_cleanup(); +} + +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)); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void) +{ + char *entries[] = { "ab/de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + + 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")); +} + +void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void) +{ + char *entries[] = { "de/" }; + + g_opts.paths.strings = entries; + g_opts.paths.count = 1; + + cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab")); + + cl_assert_equal_i(false, git_path_isdir("./testrepo/de/")); + + cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts, NULL)); + + 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")); +} diff --git a/tests-clar/clar_helpers.c b/tests-clar/clar_helpers.c index c914794380c..8d6a7024b80 100644 --- a/tests-clar/clar_helpers.c +++ b/tests-clar/clar_helpers.c @@ -9,6 +9,7 @@ void clar_on_init(void) void clar_on_shutdown(void) { git_threads_shutdown(); + giterr_clear(); } void cl_git_mkfile(const char *filename, const char *content) @@ -55,22 +56,23 @@ void cl_git_rewritefile(const char *filename, const char *new_content) char *cl_getenv(const char *name) { - wchar_t *name_utf16 = gitwin_to_utf16(name); - DWORD value_len, alloc_len; + wchar_t name_utf16[GIT_WIN_PATH]; + DWORD alloc_len; wchar_t *value_utf16; char *value_utf8; - cl_assert(name_utf16); + git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); alloc_len = GetEnvironmentVariableW(name_utf16, NULL, 0); if (alloc_len <= 0) return NULL; + alloc_len = GIT_WIN_PATH; cl_assert(value_utf16 = git__calloc(alloc_len, sizeof(wchar_t))); - value_len = GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); - cl_assert_equal_i(value_len, alloc_len - 1); + GetEnvironmentVariableW(name_utf16, value_utf16, alloc_len); - cl_assert(value_utf8 = gitwin_from_utf16(value_utf16)); + cl_assert(value_utf8 = git__malloc(alloc_len)); + git__utf16_to_8(value_utf8, value_utf16); git__free(value_utf16); @@ -79,17 +81,16 @@ char *cl_getenv(const char *name) int cl_setenv(const char *name, const char *value) { - wchar_t *name_utf16 = gitwin_to_utf16(name); - wchar_t *value_utf16 = value ? gitwin_to_utf16(value) : NULL; + wchar_t name_utf16[GIT_WIN_PATH]; + wchar_t value_utf16[GIT_WIN_PATH]; - cl_assert(name_utf16); - cl_assert(SetEnvironmentVariableW(name_utf16, value_utf16)); + git__utf8_to_16(name_utf16, GIT_WIN_PATH, name); - git__free(name_utf16); - git__free(value_utf16); + if (value != NULL) + git__utf8_to_16(value_utf16, GIT_WIN_PATH, value); + cl_assert(SetEnvironmentVariableW(name_utf16, value ? value_utf16 : NULL)); return 0; - } #else diff --git a/tests-clar/clar_libgit2.h b/tests-clar/clar_libgit2.h index eab6c3d3e8b..b4ee74cdb1d 100644 --- a/tests-clar/clar_libgit2.h +++ b/tests-clar/clar_libgit2.h @@ -25,6 +25,8 @@ */ #define cl_git_fail(expr) cl_must_fail(expr) +#define cl_assert_equal_sz(sz1,sz2) cl_assert((sz1) == (sz2)) + /* * Some utility macros for building long strings */ diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c new file mode 100644 index 00000000000..4cca15ffe03 --- /dev/null +++ b/tests-clar/clone/clone.c @@ -0,0 +1,139 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "repository.h" + +#define DO_LOCAL_TEST 0 +#define DO_LIVE_NETWORK_TESTS 0 +#define LIVE_REPO_URL "http://github.com/libgit2/node-gitteh" + + +static git_repository *g_repo; + +void test_clone_clone__initialize(void) +{ + g_repo = NULL; +} + +void test_clone_clone__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } +} + +// 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_clone__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_assert(!git_path_exists("./foo.git")); +} + + +void test_clone_clone__local(void) +{ + git_buf src = GIT_BUF_INIT; + build_local_file_url(&src, cl_fixture("testrepo.git")); + +#if DO_LOCAL_TEST + cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local", NULL, NULL, NULL)); + git_repository_free(g_repo); + git_futils_rmdir_r("./local", GIT_DIRREMOVAL_FILES_AND_DIRS); + cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git", NULL)); + git_futils_rmdir_r("./local.git", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif + + git_buf_free(&src); +} + + +void test_clone_clone__network_full(void) +{ +#if DO_LIVE_NETWORK_TESTS + git_remote *origin; + + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./test2", NULL, NULL, NULL)); + cl_assert(!git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_load(&origin, g_repo, "origin")); + git_futils_rmdir_r("./test2", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif +} + +void test_clone_clone__network_bare(void) +{ +#if DO_LIVE_NETWORK_TESTS + git_remote *origin; + + cl_git_pass(git_clone_bare(&g_repo, LIVE_REPO_URL, "test", NULL)); + cl_assert(git_repository_is_bare(g_repo)); + cl_git_pass(git_remote_load(&origin, g_repo, "origin")); + git_futils_rmdir_r("./test", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif +} + + +void test_clone_clone__already_exists(void) +{ +#if DO_LIVE_NETWORK_TESTS + /* Should pass with existing-but-empty dir */ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_repository_free(g_repo); g_repo = NULL; + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +#endif + + /* Should fail with a file */ + cl_git_mkfile("./foo", "Bar!"); + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); + + /* Should fail with existing-and-nonempty dir */ + p_mkdir("./foo", GIT_DIR_MODE); + cl_git_mkfile("./foo/bar", "Baz!"); + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", NULL, NULL, NULL)); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +} diff --git a/tests-clar/commit/parent.c b/tests-clar/commit/parent.c new file mode 100644 index 00000000000..a0075773209 --- /dev/null +++ b/tests-clar/commit/parent.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_commit *commit; + +void test_commit_parent__initialize(void) +{ + git_oid oid; + + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + + git_oid_fromstr(&oid, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); +} + +void test_commit_parent__cleanup(void) +{ + git_commit_free(commit); + git_repository_free(_repo); +} + +static void assert_nth_gen_parent(unsigned int gen, const char *expected_oid) +{ + git_commit *parent = NULL; + int error; + + error = git_commit_nth_gen_ancestor(&parent, commit, gen); + + if (expected_oid != NULL) { + cl_assert_equal_i(0, error); + cl_assert_equal_i(0, git_oid_streq(git_commit_id(parent), expected_oid)); + } else + cl_assert_equal_i(GIT_ENOTFOUND, error); + + git_commit_free(parent); +} + +/* + * $ git show be35~0 + * commit be3563ae3f795b2b4353bcce3a527ad0a4f7f644 + * + * $ git show be35~1 + * commit 9fd738e8f7967c078dceed8190330fc8648ee56a + * + * $ git show be35~3 + * commit 5b5b025afb0b4c913b4c338a42934a3863bf3644 + * + * $ git show be35~42 + * fatal: ambiguous argument 'be35~42': unknown revision or path not in the working tree. + */ +void test_commit_parent__can_retrieve_nth_generation_parent(void) +{ + assert_nth_gen_parent(0, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + assert_nth_gen_parent(1, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + assert_nth_gen_parent(3, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + assert_nth_gen_parent(42, NULL); +} diff --git a/tests-clar/commit/signature.c b/tests-clar/commit/signature.c index 290b11fa3fe..9364efb1066 100644 --- a/tests-clar/commit/signature.c +++ b/tests-clar/commit/signature.c @@ -13,17 +13,39 @@ static int try_build_signature(const char *name, const char *email, git_time_t t return error; } +static void assert_name_and_email( + const char *expected_name, + const char *expected_email, + const char *name, + const char *email) +{ + git_signature *sign; + + cl_git_pass(git_signature_new(&sign, name, email, 1234567890, 60)); + cl_assert_equal_s(expected_name, sign->name); + cl_assert_equal_s(expected_email, sign->email); + + git_signature_free(sign); +} -void test_commit_signature__create_trim(void) +void test_commit_signature__leading_and_trailing_spaces_are_trimmed(void) { - // creating a signature trims leading and trailing spaces - git_signature *sign; - cl_git_pass(git_signature_new(&sign, " nulltoken ", " emeric.fermas@gmail.com ", 1234567890, 60)); - cl_assert(strcmp(sign->name, "nulltoken") == 0); - cl_assert(strcmp(sign->email, "emeric.fermas@gmail.com") == 0); - git_signature_free((git_signature *)sign); + assert_name_and_email("nulltoken", "emeric.fermas@gmail.com", " nulltoken ", " emeric.fermas@gmail.com "); } +void test_commit_signature__angle_brackets_in_names_are_not_supported(void) +{ + cl_git_fail(try_build_signature("Haack", "phil@haack", 1234567890, 60)); + cl_git_fail(try_build_signature("", "phil@haack", 1234567890, 60)); +} + +void test_commit_signature__angle_brackets_in_email_are_not_supported(void) +{ + cl_git_fail(try_build_signature("Phil Haack", ">phil@haack", 1234567890, 60)); + cl_git_fail(try_build_signature("Phil Haack", "phil@>haack", 1234567890, 60)); + cl_git_fail(try_build_signature("Phil Haack", "", 1234567890, 60)); +} void test_commit_signature__create_empties(void) { @@ -39,21 +61,13 @@ void test_commit_signature__create_empties(void) void test_commit_signature__create_one_char(void) { // creating a one character signature - git_signature *sign; - cl_git_pass(git_signature_new(&sign, "x", "foo@bar.baz", 1234567890, 60)); - cl_assert(strcmp(sign->name, "x") == 0); - cl_assert(strcmp(sign->email, "foo@bar.baz") == 0); - git_signature_free((git_signature *)sign); + assert_name_and_email("x", "foo@bar.baz", "x", "foo@bar.baz"); } void test_commit_signature__create_two_char(void) { // creating a two character signature - git_signature *sign; - cl_git_pass(git_signature_new(&sign, "xx", "x@y.z", 1234567890, 60)); - cl_assert(strcmp(sign->name, "xx") == 0); - cl_assert(strcmp(sign->email, "x@y.z") == 0); - git_signature_free((git_signature *)sign); + assert_name_and_email("xx", "foo@bar.baz", "xx", "foo@bar.baz"); } void test_commit_signature__create_zero_char(void) diff --git a/tests-clar/config/read.c b/tests-clar/config/read.c index f33bdd89e35..fcd22463d5b 100644 --- a/tests-clar/config/read.c +++ b/tests-clar/config/read.c @@ -191,6 +191,97 @@ void test_config_read__escaping_quotes(void) git_config_free(cfg); } +static int count_cfg_entries( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)++; + return 0; +} + +static int cfg_callback_countdown( + const char *var_name, const char *value, void *payload) +{ + int *count = payload; + GIT_UNUSED(var_name); + GIT_UNUSED(value); + (*count)--; + if (*count == 0) + return -100; + return 0; +} + +void test_config_read__foreach(void) +{ + git_config *cfg; + int count, ret; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass(git_config_foreach(cfg, count_cfg_entries, &count)); + cl_assert_equal_i(5, count); + + count = 3; + cl_git_fail(ret = git_config_foreach(cfg, cfg_callback_countdown, &count)); + cl_assert_equal_i(GIT_EUSER, ret); + + git_config_free(cfg); +} + +void test_config_read__foreach_match(void) +{ + git_config *cfg; + int count; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9"))); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "core.*", count_cfg_entries, &count)); + cl_assert_equal_i(3, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, "remote\\.ab.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*url$", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*dummy.*", count_cfg_entries, &count)); + cl_assert_equal_i(2, count); + + count = 0; + cl_git_pass( + git_config_foreach_match(cfg, ".*nomatch.*", count_cfg_entries, &count)); + cl_assert_equal_i(0, count); + + git_config_free(cfg); +} + +void test_config_read__whitespace_not_required_around_assignment(void) +{ + git_config *cfg; + const char *str; + + cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config14"))); + + cl_git_pass(git_config_get_string(&str, cfg, "a.b")); + cl_assert_equal_s(str, "c"); + + cl_git_pass(git_config_get_string(&str, cfg, "d.e")); + cl_assert_equal_s(str, "f"); + + git_config_free(cfg); +} + #if 0 BEGIN_TEST(config10, "a repo's config overrides the global config") diff --git a/tests-clar/config/stress.c b/tests-clar/config/stress.c index 3de1f7692a8..6e7db6e8fb2 100644 --- a/tests-clar/config/stress.c +++ b/tests-clar/config/stress.c @@ -59,3 +59,26 @@ void test_config_stress__comments(void) git_config_free(config); } + +void test_config_stress__escape_subsection_names(void) +{ + struct git_config_file *file; + git_config *config; + const char *str; + + cl_assert(git_path_exists("git-test-config")); + cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_set_string(config, "some.sec\\tion.other", "foo")); + git_config_free(config); + + cl_git_pass(git_config_file__ondisk(&file, "git-test-config")); + cl_git_pass(git_config_new(&config)); + cl_git_pass(git_config_add_file(config, file, 0)); + + cl_git_pass(git_config_get_string(&str, config, "some.sec\\tion.other")); + cl_assert(!strcmp("foo", str)); + git_config_free(config); +} diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c index 6a718f459b5..972567e559f 100644 --- a/tests-clar/core/buffer.c +++ b/tests-clar/core/buffer.c @@ -611,3 +611,70 @@ void test_core_buffer__11(void) git_buf_free(&a); } + +void test_core_buffer__rfind_variants(void) +{ + git_buf a = GIT_BUF_INIT; + ssize_t len; + + cl_git_pass(git_buf_sets(&a, "/this/is/it/")); + + len = (ssize_t)git_buf_len(&a); + + cl_assert(git_buf_rfind(&a, '/') == len - 1); + cl_assert(git_buf_rfind_next(&a, '/') == len - 4); + + cl_assert(git_buf_rfind(&a, 'i') == len - 3); + cl_assert(git_buf_rfind_next(&a, 'i') == len - 3); + + cl_assert(git_buf_rfind(&a, 'h') == 2); + cl_assert(git_buf_rfind_next(&a, 'h') == 2); + + cl_assert(git_buf_rfind(&a, 'q') == -1); + cl_assert(git_buf_rfind_next(&a, 'q') == -1); + + git_buf_free(&a); +} + +void test_core_buffer__puts_escaped(void) +{ + git_buf a = GIT_BUF_INIT; + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "", "")); + cl_assert_equal_s("this is a test", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "t", "\\")); + cl_assert_equal_s("\\this is a \\tes\\t", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escaped(&a, "this is a test", "i ", "__")); + cl_assert_equal_s("th__is__ __is__ a__ test", a.ptr); + + git_buf_clear(&a); + cl_git_pass(git_buf_puts_escape_regex(&a, "^match\\s*[A-Z]+.*")); + cl_assert_equal_s("\\^match\\\\s\\*\\[A-Z\\]\\+\\.\\*", a.ptr); + + git_buf_free(&a); +} + +static void assert_unescape(char *expected, char *to_unescape) { + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&buf, to_unescape)); + git_buf_unescape(&buf); + cl_assert_equal_s(expected, buf.ptr); + cl_assert_equal_sz(strlen(expected), buf.size); + + git_buf_free(&buf); +} + +void test_core_buffer__unescape(void) +{ + assert_unescape("Escaped\\", "Es\\ca\\ped\\"); + assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\"); + assert_unescape("\\", "\\"); + assert_unescape("\\", "\\\\"); + assert_unescape("", ""); +} diff --git a/tests-clar/core/copy.c b/tests-clar/core/copy.c new file mode 100644 index 00000000000..2fdfed863f0 --- /dev/null +++ b/tests-clar/core/copy.c @@ -0,0 +1,126 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "path.h" +#include "posix.h" + +void test_core_copy__file(void) +{ + struct stat st; + const char *content = "This is some stuff to copy\n"; + + cl_git_mkfile("copy_me", content); + + cl_git_pass(git_futils_cp("copy_me", "copy_me_two", 0664)); + + cl_git_pass(git_path_lstat("copy_me_two", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + + cl_git_pass(p_unlink("copy_me_two")); + cl_git_pass(p_unlink("copy_me")); +} + +void test_core_copy__file_in_dir(void) +{ + struct stat st; + const char *content = "This is some other stuff to copy\n"; + + cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_mkfile("an_dir/in_a_dir/copy_me", content); + cl_assert(git_path_isdir("an_dir")); + + cl_git_pass(git_futils_mkpath2file + ("an_dir/second_dir/and_more/copy_me_two", 0775)); + + cl_git_pass(git_futils_cp + ("an_dir/in_a_dir/copy_me", + "an_dir/second_dir/and_more/copy_me_two", + 0664)); + + cl_git_pass(git_path_lstat("an_dir/second_dir/and_more/copy_me_two", &st)); + 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", GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_assert(!git_path_isdir("an_dir")); +} + +void test_core_copy__tree(void) +{ + struct stat st; + const char *content = "File content\n"; + + cl_git_pass(git_futils_mkdir("src/b", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/d", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/e", NULL, 0775, GIT_MKDIR_PATH)); + + cl_git_mkfile("src/f1", content); + cl_git_mkfile("src/b/f2", content); + cl_git_mkfile("src/c/f3", content); + cl_git_mkfile("src/c/d/f4", content); + cl_git_mkfile("src/c/d/.f5", content); + +#ifndef GIT_WIN32 + cl_assert(p_symlink("../../b/f2", "src/c/d/l1") == 0); +#endif + + cl_assert(git_path_isdir("src")); + cl_assert(git_path_isdir("src/b")); + cl_assert(git_path_isdir("src/c/d")); + cl_assert(git_path_isfile("src/c/d/f4")); + + /* copy with no empty dirs, yes links, no dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t1", GIT_CPDIR_COPY_SYMLINKS, 0) ); + + cl_assert(git_path_isdir("t1")); + cl_assert(git_path_isdir("t1/b")); + cl_assert(git_path_isdir("t1/c")); + cl_assert(git_path_isdir("t1/c/d")); + cl_assert(!git_path_isdir("t1/c/e")); + + cl_assert(git_path_isfile("t1/f1")); + cl_assert(git_path_isfile("t1/b/f2")); + cl_assert(git_path_isfile("t1/c/f3")); + cl_assert(git_path_isfile("t1/c/d/f4")); + cl_assert(!git_path_isfile("t1/c/d/.f5")); + + cl_git_pass(git_path_lstat("t1/c/f3", &st)); + cl_assert(S_ISREG(st.st_mode)); + cl_assert(strlen(content) == (size_t)st.st_size); + +#ifndef GIT_WIN32 + cl_git_pass(git_path_lstat("t1/c/d/l1", &st)); + cl_assert(S_ISLNK(st.st_mode)); +#endif + + cl_git_pass(git_futils_rmdir_r("t1", GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_assert(!git_path_isdir("t1")); + + /* copy with empty dirs, no links, yes dotfiles, no overwrite */ + + cl_git_pass( + git_futils_cp_r("src", "t2", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_COPY_DOTFILES, 0) ); + + cl_assert(git_path_isdir("t2")); + cl_assert(git_path_isdir("t2/b")); + cl_assert(git_path_isdir("t2/c")); + cl_assert(git_path_isdir("t2/c/d")); + cl_assert(git_path_isdir("t2/c/e")); + + cl_assert(git_path_isfile("t2/f1")); + cl_assert(git_path_isfile("t2/b/f2")); + cl_assert(git_path_isfile("t2/c/f3")); + cl_assert(git_path_isfile("t2/c/d/f4")); + cl_assert(git_path_isfile("t2/c/d/.f5")); + +#ifndef GIT_WIN32 + cl_git_fail(git_path_lstat("t2/c/d/l1", &st)); +#endif + + cl_git_pass(git_futils_rmdir_r("t2", GIT_DIRREMOVAL_FILES_AND_DIRS)); + cl_assert(!git_path_isdir("t2")); + + cl_git_pass(git_futils_rmdir_r("src", GIT_DIRREMOVAL_FILES_AND_DIRS)); +} diff --git a/tests-clar/core/mkdir.c b/tests-clar/core/mkdir.c new file mode 100644 index 00000000000..08ba2419e67 --- /dev/null +++ b/tests-clar/core/mkdir.c @@ -0,0 +1,182 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "path.h" +#include "posix.h" + +static void cleanup_basic_dirs(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("d0", GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("d1", GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("d2", GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("d3", GIT_DIRREMOVAL_EMPTY_HIERARCHY); + git_futils_rmdir_r("d4", GIT_DIRREMOVAL_EMPTY_HIERARCHY); +} + +void test_core_mkdir__basic(void) +{ + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", NULL, 0755, 0)); + cl_assert(git_path_isdir("d0")); + + /* make a path */ + cl_assert(!git_path_isdir("d1")); + cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", NULL, 0755, GIT_MKDIR_PATH)); + cl_assert(git_path_isdir("d1")); + cl_assert(git_path_isdir("d1/d1.1")); + cl_assert(git_path_isdir("d1/d1.1/d1.2")); + + /* make a dir exclusively */ + cl_assert(!git_path_isdir("d2")); + cl_git_pass(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + cl_assert(git_path_isdir("d2")); + + /* make exclusive failure */ + cl_git_fail(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + + /* make a path exclusively */ + cl_assert(!git_path_isdir("d3")); + cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_assert(git_path_isdir("d3")); + cl_assert(git_path_isdir("d3/d3.1/d3.2")); + + /* make exclusive path failure */ + cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + /* ??? Should EXCL only apply to the last item in the path? */ + + /* path with trailing slash? */ + cl_assert(!git_path_isdir("d4")); + cl_git_pass(git_futils_mkdir("d4/d4.1/", NULL, 0755, GIT_MKDIR_PATH)); + cl_assert(git_path_isdir("d4/d4.1")); +} + +static void cleanup_basedir(void *ref) +{ + GIT_UNUSED(ref); + git_futils_rmdir_r("base", GIT_DIRREMOVAL_EMPTY_HIERARCHY); +} + +void test_core_mkdir__with_base(void) +{ +#define BASEDIR "base/dir/here" + + cl_set_cleanup(cleanup_basedir, NULL); + + cl_git_pass(git_futils_mkdir(BASEDIR, NULL, 0755, GIT_MKDIR_PATH)); + + cl_git_pass(git_futils_mkdir("a", BASEDIR, 0755, 0)); + cl_assert(git_path_isdir(BASEDIR "/a")); + + cl_git_pass(git_futils_mkdir("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH)); + cl_assert(git_path_isdir(BASEDIR "/b/b1/b2")); + + /* exclusive with existing base */ + cl_git_pass(git_futils_mkdir("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + + /* fail: exclusive with duplicated suffix */ + cl_git_fail(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + + /* fail: exclusive with any duplicated component */ + cl_git_fail(git_futils_mkdir("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + + /* success: exclusive without path */ + cl_git_pass(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL)); + + /* path with shorter base and existing dirs */ + cl_git_pass(git_futils_mkdir("dir/here/d/", "base", 0755, GIT_MKDIR_PATH)); + cl_assert(git_path_isdir("base/dir/here/d")); + + /* fail: path with shorter base and existing dirs */ + cl_git_fail(git_futils_mkdir("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + + /* fail: base with missing components */ + cl_git_fail(git_futils_mkdir("f/", "base/missing", 0755, GIT_MKDIR_PATH)); + + /* success: shift missing component to path */ + cl_git_pass(git_futils_mkdir("missing/f/", "base/", 0755, GIT_MKDIR_PATH)); +} + +static void cleanup_chmod_root(void *ref) +{ + mode_t *mode = ref; + + if (*mode != 0) { + (void)p_umask(*mode); + git__free(mode); + } + + git_futils_rmdir_r("r", GIT_DIRREMOVAL_EMPTY_HIERARCHY); +} + +static void check_mode(mode_t expected, mode_t actual) +{ +#ifdef GIT_WIN32 + /* chmod on Win32 doesn't support exec bit, not group/world bits */ + cl_assert((expected & 0600) == (actual & 0777)); +#else + cl_assert(expected == (actual & 0777)); +#endif +} + +void test_core_mkdir__chmods(void) +{ + struct stat st; + mode_t *old = git__malloc(sizeof(mode_t)); + *old = p_umask(022); + + cl_set_cleanup(cleanup_chmod_root, old); + + cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0)); + + cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH)); + + cl_git_pass(git_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode/is/important", &st)); + check_mode(0755, st.st_mode); + + cl_git_pass(git_futils_mkdir("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + + cl_git_pass(git_path_lstat("r/mode2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode2/is2", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode2/is2/important2", &st)); + check_mode(0777, st.st_mode); + + cl_git_pass(git_futils_mkdir("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + + cl_git_pass(git_path_lstat("r/mode3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_path_lstat("r/mode3/is3", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_path_lstat("r/mode3/is3/important3", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod existing dir */ + + cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + + cl_git_pass(git_path_lstat("r/mode", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode/is", &st)); + check_mode(0755, st.st_mode); + cl_git_pass(git_path_lstat("r/mode/is/important", &st)); + check_mode(0777, st.st_mode); + + /* test that we chmod even existing dirs if CHMOD_PATH is set */ + + cl_git_pass(git_futils_mkdir("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + + cl_git_pass(git_path_lstat("r/mode2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_path_lstat("r/mode2/is2", &st)); + check_mode(0777, st.st_mode); + cl_git_pass(git_path_lstat("r/mode2/is2/important2.1", &st)); + check_mode(0777, st.st_mode); +} diff --git a/tests-clar/core/path.c b/tests-clar/core/path.c index d826612ac16..864393b705c 100644 --- a/tests-clar/core/path.c +++ b/tests-clar/core/path.c @@ -418,3 +418,54 @@ void test_core_path__13_cannot_prettify_a_non_existing_file(void) git_buf_free(&p); } + +void test_core_path__14_apply_relative(void) +{ + git_buf p = GIT_BUF_INIT; + + cl_git_pass(git_buf_sets(&p, "/this/is/a/base")); + + cl_git_pass(git_path_apply_relative(&p, "../test")); + cl_assert_equal_s("/this/is/a/test", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../the/./end")); + cl_assert_equal_s("/this/is/the/end", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "./of/this/../the/string")); + cl_assert_equal_s("/this/is/the/end/of/the/string", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../..")); + cl_assert_equal_s("/this/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_assert_equal_s("/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_assert_equal_s("/", p.ptr); + + + cl_git_pass(git_buf_sets(&p, "d:/another/test")); + + cl_git_pass(git_path_apply_relative(&p, "../../../../..")); + cl_assert_equal_s("d:/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "from/here/to/../and/./back/.")); + cl_assert_equal_s("d:/from/here/and/back/", p.ptr); + + + cl_git_pass(git_buf_sets(&p, "https://my.url.com/test.git")); + + cl_git_pass(git_path_apply_relative(&p, "../another.git")); + cl_assert_equal_s("https://my.url.com/another.git", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../full/path/url.patch")); + cl_assert_equal_s("https://my.url.com/full/path/url.patch", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "..")); + cl_assert_equal_s("https://my.url.com/full/path/", p.ptr); + + cl_git_pass(git_path_apply_relative(&p, "../../../../../")); + cl_assert_equal_s("https://", p.ptr); + + git_buf_free(&p); +} diff --git a/tests-clar/diff/blob.c b/tests-clar/diff/blob.c index 6d7ad41d627..d5cf41e9955 100644 --- a/tests-clar/diff/blob.c +++ b/tests-clar/diff/blob.c @@ -14,13 +14,13 @@ void test_diff_blob__initialize(void) memset(&opts, 0, sizeof(opts)); opts.context_lines = 1; - opts.interhunk_lines = 1; + opts.interhunk_lines = 0; memset(&expected, 0, sizeof(expected)); /* tests/resources/attr/root_test4.txt */ - cl_git_pass(git_oid_fromstrn(&oid, "fe773770c5a6", 12)); - cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 6)); + cl_git_pass(git_oid_fromstrn(&oid, "a0f7217a", 8)); + cl_git_pass(git_blob_lookup_prefix(&d, g_repo, &oid, 4)); /* alien.png */ cl_git_pass(git_oid_fromstrn(&oid, "edf3dcee", 8)); @@ -54,62 +54,63 @@ void test_diff_blob__can_compare_text_blobs(void) /* Doing the equivalent of a `git diff -U1` on these files */ + /* diff on tests/resources/attr/root_test1 */ cl_git_pass(git_diff_blobs( a, b, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_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(expected.hunks == 1); - cl_assert(expected.lines == 6); - cl_assert(expected.line_ctxt == 1); - cl_assert(expected.line_adds == 5); - cl_assert(expected.line_dels == 0); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(6, expected.lines); + cl_assert_equal_i(1, expected.line_ctxt); + cl_assert_equal_i(5, expected.line_adds); + cl_assert_equal_i(0, expected.line_dels); + /* diff on tests/resources/attr/root_test2 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( b, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_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(expected.hunks == 1); - cl_assert(expected.lines == 15); - cl_assert(expected.line_ctxt == 3); - cl_assert(expected.line_adds == 9); - cl_assert(expected.line_dels == 3); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(15, expected.lines); + cl_assert_equal_i(3, expected.line_ctxt); + cl_assert_equal_i(9, expected.line_adds); + cl_assert_equal_i(3, expected.line_dels); + /* diff on tests/resources/attr/root_test3 */ memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( a, c, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_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(expected.hunks == 1); - cl_assert(expected.lines == 13); - cl_assert(expected.line_ctxt == 0); - cl_assert(expected.line_adds == 12); - cl_assert(expected.line_dels == 1); - - opts.context_lines = 1; + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(13, expected.lines); + cl_assert_equal_i(0, expected.line_ctxt); + cl_assert_equal_i(12, expected.line_adds); + cl_assert_equal_i(1, expected.line_dels); memset(&expected, 0, sizeof(expected)); cl_git_pass(git_diff_blobs( c, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); + cl_assert_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(expected.hunks == 2); - cl_assert(expected.lines == 14); - cl_assert(expected.line_ctxt == 4); - cl_assert(expected.line_adds == 6); - cl_assert(expected.line_dels == 4); + cl_assert_equal_i(2, expected.hunks); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(4, expected.line_ctxt); + cl_assert_equal_i(6, expected.line_adds); + cl_assert_equal_i(4, expected.line_dels); git_blob_free(a); git_blob_free(b); @@ -123,14 +124,14 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_dels == 1); + cl_assert_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(expected.hunks == 1); - cl_assert(expected.hunk_old_lines == 14); - cl_assert(expected.lines == 14); - cl_assert(expected.line_dels == 14); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_old_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_dels); opts.flags |= GIT_DIFF_REVERSE; memset(&expected, 0, sizeof(expected)); @@ -138,14 +139,14 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_git_pass(git_diff_blobs( d, e, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(expected.files == 1); - cl_assert(expected.file_adds == 1); + cl_assert_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(expected.hunks == 1); - cl_assert(expected.hunk_new_lines == 14); - cl_assert(expected.lines == 14); - cl_assert(expected.line_adds == 14); + cl_assert_equal_i(1, expected.hunks); + cl_assert_equal_i(14, expected.hunk_new_lines); + cl_assert_equal_i(14, expected.lines); + cl_assert_equal_i(14, expected.line_adds); opts.flags ^= GIT_DIFF_REVERSE; memset(&expected, 0, sizeof(expected)); @@ -155,10 +156,10 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_dels == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_dels); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); memset(&expected, 0, sizeof(expected)); @@ -167,18 +168,18 @@ void test_diff_blob__can_compare_against_null_blobs(void) cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_adds == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + cl_assert_equal_i(1, expected.files); + cl_assert_equal_i(1, expected.file_adds); + cl_assert_equal_i(0, expected.hunks); + cl_assert_equal_i(0, expected.lines); } static void assert_identical_blobs_comparison(diff_expects expected) { - cl_assert(expected.files == 1); - cl_assert(expected.file_unmodified == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + 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); } void test_diff_blob__can_compare_identical_blobs(void) @@ -208,10 +209,10 @@ static void assert_binary_blobs_comparison(diff_expects expected) { cl_assert(expected.at_least_one_of_them_is_binary == true); - cl_assert(expected.files == 1); - cl_assert(expected.file_mods == 1); - cl_assert(expected.hunks == 0); - cl_assert(expected.lines == 0); + 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); } void test_diff_blob__can_compare_two_binary_blobs(void) @@ -252,3 +253,62 @@ void test_diff_blob__can_compare_a_binary_blob_and_a_text_blob(void) assert_binary_blobs_comparison(expected); } + +/* + * $ git diff fe773770 a0f7217 + * diff --git a/fe773770 b/a0f7217 + * index fe77377..a0f7217 100644 + * --- a/fe773770 + * +++ b/a0f7217 + * @@ -1,6 +1,6 @@ + * Here is some stuff at the start + * + * -This should go in one hunk + * +This should go in one hunk (first) + * + * Some additional lines + * + * @@ -8,7 +8,7 @@ Down here below the other lines + * + * With even more at the end + * + * -Followed by a second hunk of stuff + * +Followed by a second hunk of stuff (second) + * + * That happens down here + */ +void test_diff_blob__comparing_two_text_blobs_honors_interhunkcontext(void) +{ + git_blob *old_d; + git_oid old_d_oid; + + opts.context_lines = 3; + + /* tests/resources/attr/root_test1 from commit f5b0af1 */ + cl_git_pass(git_oid_fromstrn(&old_d_oid, "fe773770", 8)); + cl_git_pass(git_blob_lookup_prefix(&old_d, g_repo, &old_d_oid, 4)); + + /* Test with default inter-hunk-context (not set) => default is 0 */ + cl_git_pass(git_diff_blobs( + old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(2, expected.hunks); + + /* Test with inter-hunk-context explicitly set to 0 */ + opts.interhunk_lines = 0; + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(2, expected.hunks); + + /* Test with inter-hunk-context explicitly set to 1 */ + opts.interhunk_lines = 1; + memset(&expected, 0, sizeof(expected)); + cl_git_pass(git_diff_blobs( + old_d, d, &opts, &expected, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + cl_assert_equal_i(1, expected.hunks); + + git_blob_free(old_d); +} diff --git a/tests-clar/diff/diff_helpers.c b/tests-clar/diff/diff_helpers.c index 1d9f6121c1e..767b34392a7 100644 --- a/tests-clar/diff/diff_helpers.c +++ b/tests-clar/diff/diff_helpers.c @@ -5,7 +5,7 @@ git_tree *resolve_commit_oid_to_tree( git_repository *repo, const char *partial_oid) { - unsigned int len = (unsigned int)strlen(partial_oid); + size_t len = strlen(partial_oid); git_oid oid; git_object *obj = NULL; git_tree *tree = NULL; @@ -30,7 +30,8 @@ int diff_file_fn( GIT_UNUSED(progress); - e-> at_least_one_of_them_is_binary = delta->binary; + if (delta->binary) + e->at_least_one_of_them_is_binary = true; e->files++; switch (delta->status) { @@ -88,7 +89,8 @@ int diff_line_fn( e->line_adds++; break; case GIT_DIFF_LINE_ADD_EOFNL: - assert(0); + /* technically not a line add, but we'll count it as such */ + e->line_adds++; break; case GIT_DIFF_LINE_DELETION: e->line_dels++; @@ -102,3 +104,72 @@ int diff_line_fn( } return 0; } + +int diff_foreach_via_iterator( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb) +{ + int error; + git_diff_iterator *iter; + git_diff_delta *delta; + + if ((error = git_diff_iterator_new(&iter, diff)) < 0) + return error; + + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *hdr; + size_t hdr_len; + float progress = git_diff_iterator_progress(iter); + + /* call file_cb for this file */ + if (file_cb != NULL && file_cb(data, delta, progress) != 0) + goto abort; + + if (!hunk_cb && !line_cb) + continue; + + while (!(error = git_diff_iterator_next_hunk( + &range, &hdr, &hdr_len, iter))) { + char origin; + const char *line; + size_t line_len; + + if (hunk_cb && hunk_cb(data, delta, range, hdr, hdr_len) != 0) + goto abort; + + if (!line_cb) + continue; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) { + + if (line_cb(data, delta, range, origin, line, line_len) != 0) + goto abort; + } + + if (error && error != GIT_ITEROVER) + goto done; + } + + if (error && error != GIT_ITEROVER) + goto done; + } + +done: + git_diff_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; + +abort: + git_diff_iterator_free(iter); + giterr_clear(); + + return GIT_EUSER; +} diff --git a/tests-clar/diff/diff_helpers.h b/tests-clar/diff/diff_helpers.h index 0aaa6c11148..79e140921f9 100644 --- a/tests-clar/diff/diff_helpers.h +++ b/tests-clar/diff/diff_helpers.h @@ -45,3 +45,9 @@ extern int diff_line_fn( const char *content, size_t content_len); +extern int diff_foreach_via_iterator( + git_diff_list *diff, + void *data, + git_diff_file_fn file_cb, + git_diff_hunk_fn hunk_cb, + git_diff_data_fn line_cb); diff --git a/tests-clar/diff/diffiter.c b/tests-clar/diff/diffiter.c new file mode 100644 index 00000000000..23071e48b18 --- /dev/null +++ b/tests-clar/diff/diffiter.c @@ -0,0 +1,201 @@ +#include "clar_libgit2.h" +#include "diff_helpers.h" + +void test_diff_diffiter__initialize(void) +{ +} + +void test_diff_diffiter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_diffiter__create(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff_list *diff; + git_diff_iterator *iter; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files(void) +{ + git_repository *repo = cl_git_sandbox_init("attr"); + git_diff_list *diff; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, count = 0; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta != NULL); + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(6, count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files_2(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_list *diff; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, count = 0; + + cl_git_pass(git_diff_workdir_to_index(repo, NULL, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta != NULL); + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(8, count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__iterate_files_and_hunks(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter; + git_diff_delta *delta; + git_diff_range *range; + const char *header; + size_t header_len; + int error, file_count = 0, hunk_count = 0; + + opts.context_lines = 3; + 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_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + while ((error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(range); + hunk_count++; + } + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); +} + +void test_diff_diffiter__max_size_threshold(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter; + git_diff_delta *delta; + int error, file_count = 0, binary_count = 0, hunk_count = 0; + + opts.context_lines = 3; + 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_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + hunk_count += git_diff_iterator_num_hunks_in_file(iter); + + assert(delta->binary == 0 || delta->binary == 1); + + binary_count += delta->binary; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + + cl_assert_equal_i(13, file_count); + cl_assert_equal_i(0, binary_count); + cl_assert_equal_i(8, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + + /* try again with low file size threshold */ + + file_count = 0; + binary_count = 0; + hunk_count = 0; + + opts.context_lines = 3; + opts.interhunk_lines = 1; + 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_iterator_new(&iter, diff)); + + while ((error = git_diff_iterator_next_file(&delta, iter)) != GIT_ITEROVER) { + cl_assert_equal_i(0, error); + cl_assert(delta); + + file_count++; + + hunk_count += git_diff_iterator_num_hunks_in_file(iter); + + assert(delta->binary == 0 || delta->binary == 1); + + binary_count += delta->binary; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(delta == NULL); + + cl_assert_equal_i(13, file_count); + + /* Three files are over the 50 byte threshold: + * - staged_changes_file_deleted + * - staged_changes_modified_file + * - staged_new_file_modified_file + */ + cl_assert_equal_i(3, binary_count); + + cl_assert_equal_i(5, hunk_count); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + +} diff --git a/tests-clar/diff/index.c b/tests-clar/diff/index.c index 171815df5e0..2c6e89c4a39 100644 --- a/tests-clar/diff/index.c +++ b/tests-clar/diff/index.c @@ -44,17 +44,17 @@ void test_diff_index__0(void) * - git diff -U1 --cached 26a125ee1bf * - mv .git .gitted */ - cl_assert(exp.files == 8); - cl_assert(exp.file_adds == 3); - cl_assert(exp.file_dels == 2); - cl_assert(exp.file_mods == 3); + cl_assert_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(exp.hunks == 8); + cl_assert_equal_i(8, exp.hunks); - cl_assert(exp.lines == 11); - cl_assert(exp.line_ctxt == 3); - cl_assert(exp.line_adds == 6); - cl_assert(exp.line_dels == 2); + cl_assert_equal_i(11, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(6, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); git_diff_list_free(diff); diff = NULL; @@ -72,17 +72,67 @@ void test_diff_index__0(void) * - git diff -U1 --cached 0017bd4ab1ec3 * - mv .git .gitted */ - cl_assert(exp.files == 12); - cl_assert(exp.file_adds == 7); - cl_assert(exp.file_dels == 2); - cl_assert(exp.file_mods == 3); + cl_assert_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(exp.hunks == 12); + cl_assert_equal_i(12, exp.hunks); - cl_assert(exp.lines == 16); - cl_assert(exp.line_ctxt == 3); - cl_assert(exp.line_adds == 11); - cl_assert(exp.line_dels == 2); + cl_assert_equal_i(16, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(11, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} + +static int diff_stop_after_2_files( + void *cb_data, + git_diff_delta *delta, + float progress) +{ + diff_expects *e = cb_data; + + GIT_UNUSED(progress); + GIT_UNUSED(delta); + + e->files++; + + return (e->files == 2); +} + +void test_diff_index__1(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "26a125ee1bf"; /* the current HEAD */ + const char *b_commit = "0017bd4ab1ec3"; /* the start */ + git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit); + git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit); + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + diff_expects exp; + + cl_assert(a); + cl_assert(b); + + opts.context_lines = 1; + opts.interhunk_lines = 1; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_index_to_tree(g_repo, &opts, a, &diff)); + + cl_assert_equal_i( + GIT_EUSER, + git_diff_foreach(diff, &exp, diff_stop_after_2_files, NULL, NULL) + ); + + cl_assert_equal_i(2, exp.files); git_diff_list_free(diff); diff = NULL; diff --git a/tests-clar/diff/iterator.c b/tests-clar/diff/iterator.c index eee84810a64..c27d3fa6cf2 100644 --- a/tests-clar/diff/iterator.c +++ b/tests-clar/diff/iterator.c @@ -312,7 +312,7 @@ static const char *expected_index_oids_0[] = { "45141a79a77842c59a63229403220a4e4be74e3d", "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "fe773770c5a6cc7185580c9204b1ff18a33ff3fc", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", "3e42ffc54a663f9401cc25843d6c0e71a33e4249", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", @@ -343,7 +343,7 @@ static const char *expected_index_oids_range[] = { "45141a79a77842c59a63229403220a4e4be74e3d", "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "fe773770c5a6cc7185580c9204b1ff18a33ff3fc", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", }; void test_diff_iterator__index_range(void) diff --git a/tests-clar/diff/tree.c b/tests-clar/diff/tree.c index 4201ad2a737..f5e72cadc9c 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -39,17 +39,17 @@ void test_diff_tree__0(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 5); - cl_assert(exp.file_adds == 2); - cl_assert(exp.file_dels == 1); - cl_assert(exp.file_mods == 2); + cl_assert_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(exp.hunks == 5); + cl_assert_equal_i(5, exp.hunks); - cl_assert(exp.lines == 7 + 24 + 1 + 6 + 6); - cl_assert(exp.line_ctxt == 1); - cl_assert(exp.line_adds == 24 + 1 + 5 + 5); - cl_assert(exp.line_dels == 7 + 1); + cl_assert_equal_i(7 + 24 + 1 + 6 + 6, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(24 + 1 + 5 + 5, exp.line_adds); + cl_assert_equal_i(7 + 1, exp.line_dels); git_diff_list_free(diff); diff = NULL; @@ -61,17 +61,17 @@ void test_diff_tree__0(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 2); - cl_assert(exp.file_adds == 0); - cl_assert(exp.file_dels == 0); - cl_assert(exp.file_mods == 2); + cl_assert_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(exp.hunks == 2); + cl_assert_equal_i(2, exp.hunks); - cl_assert(exp.lines == 8 + 15); - cl_assert(exp.line_ctxt == 1); - cl_assert(exp.line_adds == 1); - cl_assert(exp.line_dels == 7 + 14); + cl_assert_equal_i(8 + 15, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(7 + 14, exp.line_dels); git_diff_list_free(diff); @@ -192,19 +192,149 @@ void test_diff_tree__bare(void) cl_git_pass(git_diff_foreach( diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert(exp.files == 3); - cl_assert(exp.file_adds == 2); - cl_assert(exp.file_dels == 0); - cl_assert(exp.file_mods == 1); + cl_assert_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(exp.hunks == 3); + cl_assert_equal_i(3, exp.hunks); - cl_assert(exp.lines == 4); - cl_assert(exp.line_ctxt == 0); - cl_assert(exp.line_adds == 3); - cl_assert(exp.line_dels == 1); + cl_assert_equal_i(4, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(3, exp.line_adds); + cl_assert_equal_i(1, exp.line_dels); git_diff_list_free(diff); git_tree_free(a); git_tree_free(b); } + +void test_diff_tree__merge(void) +{ + /* grabbed a couple of commit oids from the history of the attr repo */ + const char *a_commit = "605812a"; + const char *b_commit = "370fe9ec22"; + const char *c_commit = "f5b0af1fb4f5c"; + git_tree *a, *b, *c; + git_diff_list *diff1 = NULL, *diff2 = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("attr"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + cl_assert((c = resolve_commit_oid_to_tree(g_repo, c_commit)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, a, b, &diff1)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, NULL, c, b, &diff2)); + + git_tree_free(a); + git_tree_free(b); + git_tree_free(c); + + cl_git_pass(git_diff_merge(diff1, diff2)); + + git_diff_list_free(diff2); + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_foreach( + 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(6, exp.hunks); + + cl_assert_equal_i(59, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(36, exp.line_adds); + cl_assert_equal_i(22, exp.line_dels); + + git_diff_list_free(diff1); +} + +void test_diff_tree__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + git_tree *a, *b; + git_diff_options opts = {0}; + git_diff_list *diff = NULL; + git_diff_iterator *iter = NULL; + git_diff_delta *delta; + diff_expects exp; + int error, num_files = 0; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + memset(&exp, 0, sizeof(exp)); + + cl_git_pass(git_diff_tree_to_tree(g_repo, &opts, a, b, &diff)); + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + /* this should be exact */ + cl_assert(git_diff_iterator_progress(iter) == 0.0f); + + /* You wouldn't actually structure an iterator loop this way, but + * I have here for testing purposes of the return value + */ + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *header; + size_t header_len; + int actual_hunks = 0, num_hunks; + float expected_progress; + + num_files++; + + expected_progress = (float)num_files / 2.0f; + cl_assert(expected_progress == git_diff_iterator_progress(iter)); + + num_hunks = git_diff_iterator_num_hunks_in_file(iter); + + while (!(error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter))) + { + int actual_lines = 0; + int num_lines = git_diff_iterator_num_lines_in_hunk(iter); + char origin; + const char *line; + size_t line_len; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) + { + actual_lines++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_lines, num_lines); + + actual_hunks++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_hunks, num_hunks); + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(2, num_files); + cl_assert(git_diff_iterator_progress(iter) == 1.0f); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + diff = NULL; + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 801439e3079..40a8885442b 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -17,6 +17,7 @@ void test_diff_workdir__to_index(void) git_diff_options opts = {0}; git_diff_list *diff = NULL; diff_expects exp; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -24,33 +25,39 @@ void test_diff_workdir__to_index(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - - /* to generate these values: - * - cd to tests/resources/status, - * - mv .gitted .git - * - git diff --name-status - * - git diff - * - mv .git .gitted - */ - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); - - cl_assert_equal_i(8, exp.hunks); - - cl_assert_equal_i(14, exp.lines); - cl_assert_equal_i(5, exp.line_ctxt); - cl_assert_equal_i(4, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + + /* to generate these values: + * - cd to tests/resources/status, + * - mv .gitted .git + * - git diff --name-status + * - git diff + * - mv .git .gitted + */ + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + + cl_assert_equal_i(8, exp.hunks); + + cl_assert_equal_i(14, exp.lines); + cl_assert_equal_i(5, exp.line_ctxt); + cl_assert_equal_i(4, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } git_diff_list_free(diff); } @@ -65,6 +72,7 @@ void test_diff_workdir__to_tree(void) git_diff_list *diff = NULL; git_diff_list *diff2 = NULL; diff_expects exp; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -75,8 +83,6 @@ void test_diff_workdir__to_tree(void) opts.interhunk_lines = 1; opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED; - memset(&exp, 0, sizeof(exp)); - /* You can't really generate the equivalent of git_diff_workdir_to_tree() * using C git. It really wants to interpose the index into the diff. * @@ -89,15 +95,23 @@ void test_diff_workdir__to_tree(void) */ cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(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); + } /* 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 @@ -117,22 +131,30 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - 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); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); - cl_assert_equal_i(11, exp.hunks); + 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(17, exp.lines); - cl_assert_equal_i(4, exp.line_ctxt); - cl_assert_equal_i(8, exp.line_adds); - cl_assert_equal_i(5, exp.line_dels); + cl_assert_equal_i(11, exp.hunks); + + cl_assert_equal_i(17, exp.lines); + cl_assert_equal_i(4, exp.line_ctxt); + cl_assert_equal_i(8, exp.line_adds); + cl_assert_equal_i(5, exp.line_dels); + } git_diff_list_free(diff); diff = NULL; @@ -146,22 +168,30 @@ void test_diff_workdir__to_tree(void) cl_git_pass(git_diff_merge(diff, diff2)); git_diff_list_free(diff2); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(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(12, exp.hunks); + cl_assert_equal_i(12, exp.hunks); - cl_assert_equal_i(19, exp.lines); - cl_assert_equal_i(3, exp.line_ctxt); - cl_assert_equal_i(12, exp.line_adds); - cl_assert_equal_i(4, exp.line_dels); + cl_assert_equal_i(19, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(12, exp.line_adds); + cl_assert_equal_i(4, exp.line_dels); + } git_diff_list_free(diff); @@ -175,6 +205,7 @@ void test_diff_workdir__to_index_with_pathspec(void) git_diff_list *diff = NULL; diff_expects exp; char *pathspec = NULL; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -184,62 +215,93 @@ void test_diff_workdir__to_index_with_pathspec(void) opts.pathspec.strings = &pathspec; opts.pathspec.count = 1; - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(13, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(4, exp.file_dels); - cl_assert_equal_i(4, exp.file_mods); - cl_assert_equal_i(1, exp.file_ignored); - cl_assert_equal_i(4, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(4, exp.file_dels); + cl_assert_equal_i(4, exp.file_mods); + cl_assert_equal_i(1, exp.file_ignored); + cl_assert_equal_i(4, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "modified_file"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "subdir"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(1, exp.file_dels); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(1, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(1, exp.file_dels); + cl_assert_equal_i(1, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(1, exp.file_untracked); + } git_diff_list_free(diff); - memset(&exp, 0, sizeof(exp)); pathspec = "*_deleted"; cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); - cl_assert_equal_i(2, exp.files); - cl_assert_equal_i(0, exp.file_adds); - cl_assert_equal_i(2, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.file_ignored); - cl_assert_equal_i(0, exp.file_untracked); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, NULL, NULL)); + else + cl_git_pass(git_diff_foreach(diff, &exp, diff_file_fn, NULL, NULL)); + + cl_assert_equal_i(2, exp.files); + cl_assert_equal_i(0, exp.file_adds); + cl_assert_equal_i(2, exp.file_dels); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.file_ignored); + cl_assert_equal_i(0, exp.file_untracked); + } git_diff_list_free(diff); } @@ -249,6 +311,7 @@ void test_diff_workdir__filemode_changes(void) git_config *cfg; git_diff_list *diff = NULL; diff_expects exp; + int use_iterator; if (!cl_is_chmod_supported()) return; @@ -262,13 +325,20 @@ void test_diff_workdir__filemode_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(0, exp.files); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + 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.hunks); + } git_diff_list_free(diff); @@ -278,13 +348,20 @@ void test_diff_workdir__filemode_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, NULL, &diff)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); - cl_assert_equal_i(1, exp.files); - cl_assert_equal_i(1, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(0, exp.hunks); + } git_diff_list_free(diff); @@ -347,6 +424,7 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) diff_expects exp; char *pathspec = "staged_changes_modified_file"; git_tree *tree; + int use_iterator; /* For this file, * - head->index diff has 1 line of context, 1 line of diff @@ -366,46 +444,70 @@ void test_diff_workdir__head_index_and_workdir_all_differ(void) 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)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - 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(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); - - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - 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(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(2, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_w2i, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(2, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } cl_git_pass(git_diff_merge(diff_i2t, diff_w2i)); - memset(&exp, 0, sizeof(exp)); - cl_git_pass(git_diff_foreach( - 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(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(2, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff_i2t, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(2, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff_i2t); git_diff_list_free(diff_w2i); @@ -419,6 +521,7 @@ void test_diff_workdir__eof_newline_changes(void) git_diff_list *diff = NULL; diff_expects exp; char *pathspec = "current_file"; + int use_iterator; g_repo = cl_git_sandbox_init("status"); @@ -427,18 +530,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - 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_adds); - cl_assert_equal_i(0, exp.file_dels); - cl_assert_equal_i(0, exp.file_mods); - cl_assert_equal_i(0, exp.hunks); - cl_assert_equal_i(0, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(0, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + 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_adds); + cl_assert_equal_i(0, exp.file_dels); + cl_assert_equal_i(0, exp.file_mods); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(0, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff); @@ -446,18 +557,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - 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(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(1, exp.hunks); - cl_assert_equal_i(2, exp.lines); - cl_assert_equal_i(1, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(0, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(1, exp.hunks); + cl_assert_equal_i(2, exp.lines); + cl_assert_equal_i(1, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(0, exp.line_dels); + } git_diff_list_free(diff); @@ -465,18 +584,26 @@ void test_diff_workdir__eof_newline_changes(void) cl_git_pass(git_diff_workdir_to_index(g_repo, &opts, &diff)); - 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(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(1, exp.hunks); - cl_assert_equal_i(3, exp.lines); - cl_assert_equal_i(0, exp.line_ctxt); - cl_assert_equal_i(1, exp.line_adds); - cl_assert_equal_i(2, exp.line_dels); + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, &exp, diff_file_fn, diff_hunk_fn, diff_line_fn)); + else + cl_git_pass(git_diff_foreach( + 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(1, exp.hunks); + cl_assert_equal_i(3, exp.lines); + cl_assert_equal_i(0, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(2, exp.line_dels); + } git_diff_list_free(diff); } @@ -543,3 +670,94 @@ void test_diff_workdir__eof_newline_changes(void) * * Expect 13 files, 0 ADD, 4 DEL, 4 MOD, 1 IGN, 4 UNTR */ + + +void test_diff_workdir__larger_hunks(void) +{ + const char *a_commit = "d70d245ed97ed2aa596dd1af6536e4bfdb047b69"; + const char *b_commit = "7a9e0b02e63179929fed24f0a3e0f19168114d10"; + git_tree *a, *b; + git_diff_options opts = {0}; + int i, error; + + g_repo = cl_git_sandbox_init("diff"); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, a_commit)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, b_commit)) != NULL); + + opts.context_lines = 1; + opts.interhunk_lines = 0; + + for (i = 0; i <= 2; ++i) { + git_diff_list *diff = NULL; + git_diff_iterator *iter = NULL; + git_diff_delta *delta; + int num_files = 0; + + /* 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)); + break; + case 1: + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, a, &diff)); + break; + case 2: + cl_git_pass(git_diff_workdir_to_tree(g_repo, &opts, b, &diff)); + break; + } + + cl_git_pass(git_diff_iterator_new(&iter, diff)); + + cl_assert(git_diff_iterator_progress(iter) == 0.0f); + + while (!(error = git_diff_iterator_next_file(&delta, iter))) { + git_diff_range *range; + const char *header; + size_t header_len; + int actual_hunks = 0, num_hunks; + float expected_progress; + + num_files++; + + expected_progress = (float)num_files / 2.0f; + cl_assert(expected_progress == git_diff_iterator_progress(iter)); + + num_hunks = git_diff_iterator_num_hunks_in_file(iter); + + while (!(error = git_diff_iterator_next_hunk( + &range, &header, &header_len, iter))) + { + int actual_lines = 0; + int num_lines = git_diff_iterator_num_lines_in_hunk(iter); + char origin; + const char *line; + size_t line_len; + + while (!(error = git_diff_iterator_next_line( + &origin, &line, &line_len, iter))) + { + actual_lines++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_lines, num_lines); + + actual_hunks++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(actual_hunks, num_hunks); + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(2, num_files); + cl_assert(git_diff_iterator_progress(iter) == 1.0f); + + git_diff_iterator_free(iter); + git_diff_list_free(diff); + } + + git_tree_free(a); + git_tree_free(b); +} diff --git a/tests-clar/index/filemodes.c b/tests-clar/index/filemodes.c index 8bd35ddab52..75c94e8e775 100644 --- a/tests-clar/index/filemodes.c +++ b/tests-clar/index/filemodes.c @@ -100,40 +100,40 @@ void test_index_filemodes__untrusted(void) /* 1 - add 0644 over existing 0644 -> expect 0644 */ replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); - add_and_check_mode(index, "exec_off", 0100644); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); /* 2 - add 0644 over existing 0755 -> expect 0755 */ replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); - add_and_check_mode(index, "exec_on", 0100755); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); /* 3 - add 0755 over existing 0644 -> expect 0644 */ replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); - add_and_check_mode(index, "exec_off", 0100644); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); /* 4 - add 0755 over existing 0755 -> expect 0755 */ replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); - add_and_check_mode(index, "exec_on", 0100755); + 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", 0100644); + 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", 0100755); + 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", 0100644); + 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", 0100755); + append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); /* 9 - 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", 0100644); + add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); /* this test won't give predictable results on a platform * that doesn't support filemodes correctly, so skip it. @@ -142,7 +142,7 @@ void test_index_filemodes__untrusted(void) /* 10 - 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", 0100755); + add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); } git_index_free(index); @@ -168,45 +168,45 @@ void test_index_filemodes__trusted(void) /* 1 - add 0644 over existing 0644 -> expect 0644 */ replace_file_with_mode("exec_off", "filemodes/exec_off.0", 0644); - add_and_check_mode(index, "exec_off", 0100644); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB); /* 2 - add 0644 over existing 0755 -> expect 0644 */ replace_file_with_mode("exec_on", "filemodes/exec_on.0", 0644); - add_and_check_mode(index, "exec_on", 0100644); + add_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB); /* 3 - add 0755 over existing 0644 -> expect 0755 */ replace_file_with_mode("exec_off", "filemodes/exec_off.1", 0755); - add_and_check_mode(index, "exec_off", 0100755); + add_and_check_mode(index, "exec_off", GIT_FILEMODE_BLOB_EXECUTABLE); /* 4 - add 0755 over existing 0755 -> expect 0755 */ replace_file_with_mode("exec_on", "filemodes/exec_on.1", 0755); - add_and_check_mode(index, "exec_on", 0100755); + 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", 0100644); + 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", 0100644); + 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", 0100755); + 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", 0100755); + append_and_check_mode(index, "exec_on", GIT_FILEMODE_BLOB_EXECUTABLE); /* 9 - 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", 0100644); + add_and_check_mode(index, "new_off", GIT_FILEMODE_BLOB); /* 10 - 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", 0100755); + add_and_check_mode(index, "new_on", GIT_FILEMODE_BLOB_EXECUTABLE); git_index_free(index); } diff --git a/tests-clar/index/read_tree.c b/tests-clar/index/read_tree.c index c657d4f712a..0479332dce2 100644 --- a/tests-clar/index/read_tree.c +++ b/tests-clar/index/read_tree.c @@ -33,7 +33,7 @@ void test_index_read_tree__read_write_involution(void) /* read-tree */ git_tree_lookup(&tree, repo, &expected); - cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_read_tree(index, tree, NULL)); git_tree_free(tree); cl_git_pass(git_tree_create_fromindex(&tree_oid, index)); diff --git a/tests-clar/network/remotelocal.c b/tests-clar/network/remotelocal.c index 41922975e74..3ff6197485f 100644 --- a/tests-clar/network/remotelocal.c +++ b/tests-clar/network/remotelocal.c @@ -107,7 +107,7 @@ void test_network_remotelocal__retrieve_advertised_references(void) cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 21); + cl_assert_equal_i(how_many_refs, 26); } void test_network_remotelocal__retrieve_advertised_references_from_spaced_repository(void) @@ -121,7 +121,7 @@ void test_network_remotelocal__retrieve_advertised_references_from_spaced_reposi cl_git_pass(git_remote_ls(remote, &count_ref__cb, &how_many_refs)); - cl_assert_equal_i(how_many_refs, 21); + cl_assert_equal_i(how_many_refs, 26); git_remote_free(remote); /* Disconnect from the "spaced repo" before the cleanup */ remote = NULL; diff --git a/tests-clar/network/remotes.c b/tests-clar/network/remotes.c index eb7947dfbfc..c7ee863e78e 100644 --- a/tests-clar/network/remotes.c +++ b/tests-clar/network/remotes.c @@ -2,6 +2,7 @@ #include "buffer.h" #include "refspec.h" #include "transport.h" +#include "remote.h" static git_remote *_remote; static git_repository *_repo; @@ -27,8 +28,37 @@ void test_network_remotes__cleanup(void) void test_network_remotes__parsing(void) { + git_remote *_remote2 = NULL; + cl_assert_equal_s(git_remote_name(_remote), "test"); cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert(git_remote_pushurl(_remote) == NULL); + + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_FETCH), + "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIR_PUSH), + "git://github.com/libgit2/libgit2"); + + cl_git_pass(git_remote_load(&_remote2, _repo, "test_with_pushurl")); + cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl"); + cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2"); + + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_FETCH), + "git://github.com/libgit2/fetchlibgit2"); + cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIR_PUSH), + "git://github.com/libgit2/pushlibgit2"); + + git_remote_free(_remote2); +} + +void test_network_remotes__pushurl(void) +{ + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/notlibgit2")); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/notlibgit2"); + + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__parsing_ssh_remote(void) @@ -81,6 +111,7 @@ void test_network_remotes__save(void) cl_git_pass(git_remote_new(&_remote, _repo, "upstream", "git://github.com/libgit2/libgit2", NULL)); cl_git_pass(git_remote_set_fetchspec(_remote, "refs/heads/*:refs/remotes/upstream/*")); cl_git_pass(git_remote_set_pushspec(_remote, "refs/heads/*:refs/heads/*")); + cl_git_pass(git_remote_set_pushurl(_remote, "git://github.com/libgit2/libgit2_push")); cl_git_pass(git_remote_save(_remote)); git_remote_free(_remote); _remote = NULL; @@ -98,6 +129,18 @@ void test_network_remotes__save(void) cl_assert(_refspec != NULL); cl_assert_equal_s(git_refspec_src(_refspec), "refs/heads/*"); cl_assert_equal_s(git_refspec_dst(_refspec), "refs/heads/*"); + + cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2"); + cl_assert_equal_s(git_remote_pushurl(_remote), "git://github.com/libgit2/libgit2_push"); + + /* remove the pushurl again and see if we can save that too */ + cl_git_pass(git_remote_set_pushurl(_remote, NULL)); + cl_git_pass(git_remote_save(_remote)); + git_remote_free(_remote); + _remote = NULL; + + cl_git_pass(git_remote_load(&_remote, _repo, "upstream")); + cl_assert(git_remote_pushurl(_remote) == NULL); } void test_network_remotes__fnmatch(void) @@ -143,13 +186,13 @@ void test_network_remotes__list(void) git_config *cfg; cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 1); + cl_assert(list.count == 3); git_strarray_free(&list); cl_git_pass(git_repository_config(&cfg, _repo)); cl_git_pass(git_config_set_string(cfg, "remote.specless.url", "http://example.com")); cl_git_pass(git_remote_list(&list, _repo)); - cl_assert(list.count == 2); + cl_assert(list.count == 4); git_strarray_free(&list); git_config_free(cfg); @@ -180,4 +223,5 @@ void test_network_remotes__add(void) cl_assert(!strcmp(git_refspec_src(_refspec), "refs/heads/*")); cl_assert(git_refspec_force(_refspec) == 1); cl_assert(!strcmp(git_refspec_dst(_refspec), "refs/remotes/addtest/*")); + cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2"); } diff --git a/tests-clar/notes/notes.c b/tests-clar/notes/notes.c index e1387782e70..dfd7f5231da 100644 --- a/tests-clar/notes/notes.c +++ b/tests-clar/notes/notes.c @@ -95,11 +95,39 @@ void test_notes_notes__can_retrieve_a_list_of_notes_for_a_given_namespace(void) create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); - cl_git_pass(git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes)); + cl_git_pass(git_note_foreach +(_repo, "refs/notes/i-can-see-dead-notes", note_list_cb, &retrieved_notes)); cl_assert_equal_i(4, retrieved_notes); } +static int note_cancel_cb(git_note_data *note_data, void *payload) +{ + unsigned int *count = (unsigned int *)payload; + + GIT_UNUSED(note_data); + + (*count)++; + + return (*count > 2); +} + +void test_notes_notes__can_cancel_foreach(void) +{ + git_oid note_oid1, note_oid2, note_oid3, note_oid4; + unsigned int retrieved_notes = 0; + + create_note(¬e_oid1, "refs/notes/i-can-see-dead-notes", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", "I decorate a65f\n"); + create_note(¬e_oid2, "refs/notes/i-can-see-dead-notes", "c47800c7266a2be04c571c04d5a6614691ea99bd", "I decorate c478\n"); + create_note(¬e_oid3, "refs/notes/i-can-see-dead-notes", "9fd738e8f7967c078dceed8190330fc8648ee56a", "I decorate 9fd7 and 4a20\n"); + create_note(¬e_oid4, "refs/notes/i-can-see-dead-notes", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", "I decorate 9fd7 and 4a20\n"); + + cl_assert_equal_i( + GIT_EUSER, + git_note_foreach(_repo, "refs/notes/i-can-see-dead-notes", + note_cancel_cb, &retrieved_notes)); +} + void test_notes_notes__retrieving_a_list_of_notes_for_an_unknown_namespace_returns_ENOTFOUND(void) { int error; diff --git a/tests-clar/object/blob/fromchunks.c b/tests-clar/object/blob/fromchunks.c index 228e969b657..dc57d4fbe41 100644 --- a/tests-clar/object/blob/fromchunks.c +++ b/tests-clar/object/blob/fromchunks.c @@ -30,7 +30,7 @@ static int text_chunked_source_cb(char *content, size_t max_length, void *payloa return 0; strcpy(content, textual_content); - return strlen(textual_content); + return (int)strlen(textual_content); } void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void) diff --git a/tests-clar/object/commit/commitstagedfile.c b/tests-clar/object/commit/commitstagedfile.c index 628ef43c25a..882fb49ae64 100644 --- a/tests-clar/object/commit/commitstagedfile.c +++ b/tests-clar/object/commit/commitstagedfile.c @@ -109,7 +109,7 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); - cl_git_pass(git_message_prettify(buffer, 128, "Initial commit", 0)); + cl_assert_equal_i(16, git_message_prettify(buffer, 128, "Initial commit", 0)); cl_git_pass(git_commit_create_v( &commit_oid, @@ -128,3 +128,68 @@ 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/peel.c b/tests-clar/object/peel.c new file mode 100644 index 00000000000..f748be7f435 --- /dev/null +++ b/tests-clar/object/peel.c @@ -0,0 +1,104 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_object_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_object_peel__cleanup(void) +{ + git_repository_free(g_repo); +} + +static void assert_peel( + const char *sha, + git_otype requested_type, + const char* expected_sha, + git_otype expected_type) +{ + git_oid oid, expected_oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_git_pass(git_object_peel(&peeled, obj, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + + cl_assert_equal_i(expected_type, git_object_type(peeled)); + + git_object_free(peeled); + git_object_free(obj); +} + +static void assert_peel_error(int error, const char *sha, git_otype requested_type) +{ + git_oid oid; + git_object *obj; + git_object *peeled; + + cl_git_pass(git_oid_fromstr(&oid, sha)); + cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY)); + + cl_assert_equal_i(error, git_object_peel(&peeled, obj, requested_type)); + + git_object_free(obj); +} + +void test_object_peel__peeling_an_object_into_its_own_type_returns_another_instance_of_it(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG, + "7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TAG); + assert_peel("53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB, + "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_BLOB); +} + +void test_object_peel__can_peel_a_tag(void) +{ + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); +} + +void test_object_peel__can_peel_a_commit(void) +{ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); +} + +void test_object_peel__cannot_peel_a_tree(void) +{ + assert_peel_error(GIT_ERROR, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_BLOB); +} + +void test_object_peel__cannot_peel_a_blob(void) +{ + assert_peel_error(GIT_ERROR, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_COMMIT); +} + +void test_object_peel__target_any_object_for_type_change(void) +{ + /* tag to commit */ + assert_peel("7b4384978d2493e851f9cca7858815fac9b10980", GIT_OBJ_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + + /* commit to tree */ + assert_peel("e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_ANY, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + + /* fail to peel tree */ + assert_peel_error(GIT_ERROR, "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_ANY); + + /* fail to peel blob */ + assert_peel_error(GIT_ERROR, "0266163a49e280c4f5ed1e08facd36a2bd716bcf", GIT_OBJ_ANY); +} diff --git a/tests-clar/object/tree/attributes.c b/tests-clar/object/tree/attributes.c new file mode 100644 index 00000000000..054f67137bf --- /dev/null +++ b/tests-clar/object/tree/attributes.c @@ -0,0 +1,115 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static const char *blob_oid = "3d0970ec547fc41ef8a5882dde99c6adce65b021"; +static const char *tree_oid = "1b05fdaa881ee45b48cbaa5e9b037d667a47745e"; + +void test_object_tree_attributes__ensure_correctness_of_attributes_on_insertion(void) +{ + git_treebuilder *builder; + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, blob_oid)); + + cl_git_pass(git_treebuilder_create(&builder, NULL)); + + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0777777)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0100666)); + cl_git_fail(git_treebuilder_insert(NULL, builder, "one.txt", &oid, (git_filemode_t)0000001)); + + git_treebuilder_free(builder); +} + +void test_object_tree_attributes__group_writable_tree_entries_created_with_an_antique_git_version_can_still_be_accessed(void) +{ + git_repository *repo; + git_oid tid; + git_tree *tree; + const git_tree_entry *entry; + + cl_git_pass(git_repository_open(&repo, cl_fixture("deprecated-mode.git"))); + + cl_git_pass(git_oid_fromstr(&tid, tree_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &tid)); + + entry = git_tree_entry_byname(tree, "old_mode.txt"); + cl_assert_equal_i( + GIT_FILEMODE_BLOB_GROUP_WRITABLE, + git_tree_entry_filemode(entry)); + + git_tree_free(tree); + git_repository_free(repo); +} + +void test_object_tree_attributes__normalize_attributes_when_inserting_in_a_new_tree(void) +{ + git_repository *repo; + git_treebuilder *builder; + git_oid bid, tid; + git_tree *tree; + const git_tree_entry *entry; + + repo = cl_git_sandbox_init("deprecated-mode.git"); + + cl_git_pass(git_oid_fromstr(&bid, blob_oid)); + + cl_git_pass(git_treebuilder_create(&builder, NULL)); + + cl_git_pass(git_treebuilder_insert( + &entry, + builder, + "normalized.txt", + &bid, + GIT_FILEMODE_BLOB_GROUP_WRITABLE)); + + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + cl_git_pass(git_treebuilder_write(&tid, repo, builder)); + git_treebuilder_free(builder); + + cl_git_pass(git_tree_lookup(&tree, repo, &tid)); + + entry = git_tree_entry_byname(tree, "normalized.txt"); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + git_tree_free(tree); + cl_git_sandbox_cleanup(); +} + +void test_object_tree_attributes__normalize_attributes_when_creating_a_tree_from_an_existing_one(void) +{ + git_repository *repo; + git_treebuilder *builder; + git_oid tid, tid2; + git_tree *tree; + const git_tree_entry *entry; + + repo = cl_git_sandbox_init("deprecated-mode.git"); + + cl_git_pass(git_oid_fromstr(&tid, tree_oid)); + cl_git_pass(git_tree_lookup(&tree, repo, &tid)); + + cl_git_pass(git_treebuilder_create(&builder, tree)); + + entry = git_treebuilder_get(builder, "old_mode.txt"); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + cl_git_pass(git_treebuilder_write(&tid2, repo, builder)); + git_treebuilder_free(builder); + git_tree_free(tree); + + cl_git_pass(git_tree_lookup(&tree, repo, &tid2)); + entry = git_tree_entry_byname(tree, "old_mode.txt"); + cl_assert_equal_i( + GIT_FILEMODE_BLOB, + git_tree_entry_filemode(entry)); + + git_tree_free(tree); + cl_git_sandbox_cleanup(); +} diff --git a/tests-clar/object/tree/frompath.c b/tests-clar/object/tree/frompath.c index ef092d1db4e..fd425517cdb 100644 --- a/tests-clar/object/tree/frompath.c +++ b/tests-clar/object/tree/frompath.c @@ -46,12 +46,12 @@ void test_object_tree_frompath__retrieve_tree_from_path_to_treeentry(void) assert_tree_from_path(tree, "ab/", "ab"); assert_tree_from_path(tree, "ab/de/", "de"); - cl_must_fail(git_tree_entry_bypath(&e, tree, "i-do-not-exist.txt")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "README/")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "ab/de/fgh/i-do-not-exist.txt")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "nope/de/fgh/1.txt")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt")); - cl_must_fail(git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt/")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "i-do-not-exist.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "README/")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/de/fgh/i-do-not-exist.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "nope/de/fgh/1.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt")); + cl_assert_equal_i(GIT_ENOTFOUND, git_tree_entry_bypath(&e, tree, "ab/me-neither/fgh/2.txt/")); } void test_object_tree_frompath__fail_when_processing_an_invalid_path(void) diff --git a/tests-clar/object/tree/walk.c b/tests-clar/object/tree/walk.c new file mode 100644 index 00000000000..58b0bca4c21 --- /dev/null +++ b/tests-clar/object/tree/walk.c @@ -0,0 +1,103 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static const char *tree_oid = "1810dff58d8a660512d4832e740f692884338ccd"; +static git_repository *g_repo; + +void test_object_tree_walk__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); +} + +void test_object_tree_walk__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int treewalk_count_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + int *count = payload; + + GIT_UNUSED(root); + GIT_UNUSED(entry); + + (*count) += 1; + + return 0; +} + +void test_object_tree_walk__0(void) +{ + git_oid id; + git_tree *tree; + int ct; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + ct = 0; + cl_git_pass(git_tree_walk(tree, treewalk_count_cb, GIT_TREEWALK_PRE, &ct)); + cl_assert_equal_i(3, ct); + + ct = 0; + cl_git_pass(git_tree_walk(tree, treewalk_count_cb, GIT_TREEWALK_POST, &ct)); + cl_assert_equal_i(3, ct); + + git_tree_free(tree); +} + + +static int treewalk_stop_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + int *count = payload; + + GIT_UNUSED(root); + GIT_UNUSED(entry); + + (*count) += 1; + + return (*count == 2) ? -1 : 0; +} + +static int treewalk_stop_immediately_cb( + const char *root, const git_tree_entry *entry, void *payload) +{ + GIT_UNUSED(root); + GIT_UNUSED(entry); + GIT_UNUSED(payload); + return -100; +} + +void test_object_tree_walk__1(void) +{ + git_oid id; + git_tree *tree; + int ct; + + git_oid_fromstr(&id, tree_oid); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); + + ct = 0; + cl_assert_equal_i( + GIT_EUSER, git_tree_walk(tree, treewalk_stop_cb, GIT_TREEWALK_PRE, &ct)); + cl_assert_equal_i(2, ct); + + ct = 0; + cl_assert_equal_i( + GIT_EUSER, git_tree_walk(tree, treewalk_stop_cb, GIT_TREEWALK_POST, &ct)); + cl_assert_equal_i(2, ct); + + cl_assert_equal_i( + GIT_EUSER, git_tree_walk( + tree, treewalk_stop_immediately_cb, GIT_TREEWALK_PRE, NULL)); + + cl_assert_equal_i( + GIT_EUSER, git_tree_walk( + tree, treewalk_stop_immediately_cb, GIT_TREEWALK_POST, NULL)); + + git_tree_free(tree); +} diff --git a/tests-clar/object/tree/write.c b/tests-clar/object/tree/write.c index 8b0f3417fa6..657bed28912 100644 --- a/tests-clar/object/tree/write.c +++ b/tests-clar/object/tree/write.c @@ -35,11 +35,16 @@ void test_object_tree_write__from_memory(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); cl_git_pass(git_treebuilder_create(&builder, tree)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "", &bid, 0100644)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "/", &bid, 0100644)); - cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt", &bid, 0100644)); + cl_git_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)); + + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); - cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); cl_git_pass(git_treebuilder_write(&rid, g_repo, builder)); cl_assert(git_oid_cmp(&rid, &id2) == 0); @@ -63,14 +68,16 @@ void test_object_tree_write__subtree(void) //create subtree cl_git_pass(git_treebuilder_create(&builder, NULL)); - cl_git_pass(git_treebuilder_insert(NULL,builder,"new.txt",&bid,0100644)); //-V536 + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); //-V536 cl_git_pass(git_treebuilder_write(&subtree_id, g_repo, builder)); git_treebuilder_free(builder); // create parent tree cl_git_pass(git_tree_lookup(&tree, g_repo, &id)); cl_git_pass(git_treebuilder_create(&builder, tree)); - cl_git_pass(git_treebuilder_insert(NULL,builder,"new",&subtree_id,040000)); //-V536 + cl_git_pass(git_treebuilder_insert( + NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); //-V536 cl_git_pass(git_treebuilder_write(&id_hiearar, g_repo, builder)); git_treebuilder_free(builder); git_tree_free(tree); @@ -96,23 +103,23 @@ void test_object_tree_write__sorted_subtrees(void) unsigned int attr; const char *filename; } entries[] = { - { 0100644, ".gitattributes" }, - { 0100644, ".gitignore" }, - { 0100644, ".htaccess" }, - { 0100644, "Capfile" }, - { 0100644, "Makefile"}, - { 0100644, "README"}, - { 0040000, "app"}, - { 0040000, "cake"}, - { 0040000, "config"}, - { 0100644, "c"}, - { 0100644, "git_test.txt"}, - { 0100644, "htaccess.htaccess"}, - { 0100644, "index.php"}, - { 0040000, "plugins"}, - { 0040000, "schemas"}, - { 0040000, "ssl-certs"}, - { 0040000, "vendors"} + { GIT_FILEMODE_BLOB, ".gitattributes" }, + { GIT_FILEMODE_BLOB, ".gitignore" }, + { GIT_FILEMODE_BLOB, ".htaccess" }, + { GIT_FILEMODE_BLOB, "Capfile" }, + { GIT_FILEMODE_BLOB, "Makefile"}, + { GIT_FILEMODE_BLOB, "README"}, + { GIT_FILEMODE_TREE, "app"}, + { GIT_FILEMODE_TREE, "cake"}, + { GIT_FILEMODE_TREE, "config"}, + { GIT_FILEMODE_BLOB, "c"}, + { GIT_FILEMODE_BLOB, "git_test.txt"}, + { GIT_FILEMODE_BLOB, "htaccess.htaccess"}, + { GIT_FILEMODE_BLOB, "index.php"}, + { GIT_FILEMODE_TREE, "plugins"}, + { GIT_FILEMODE_TREE, "schemas"}, + { GIT_FILEMODE_TREE, "ssl-certs"}, + { GIT_FILEMODE_TREE, "vendors"} }; git_oid blank_oid, tree_oid; diff --git a/tests-clar/odb/foreach.c b/tests-clar/odb/foreach.c new file mode 100644 index 00000000000..c1304a2e4f3 --- /dev/null +++ b/tests-clar/odb/foreach.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "git2/odb_backend.h" +#include "pack.h" + +static git_odb *_odb; +static git_repository *_repo; +static int nobj; + +void test_odb_foreach__cleanup(void) +{ + git_odb_free(_odb); + git_repository_free(_repo); + + _odb = NULL; + _repo = NULL; +} + +static int foreach_cb(git_oid *oid, void *data) +{ + GIT_UNUSED(data); + GIT_UNUSED(oid); + + nobj++; + + return 0; +} + +/* + * $ git --git-dir tests-clar/resources/testrepo.git count-objects --verbose + * count: 43 + * size: 3 + * in-pack: 1640 + * packs: 3 + * size-pack: 425 + * prune-packable: 0 + * garbage: 0 + */ +void test_odb_foreach__foreach(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + git_repository_odb(&_odb, _repo); + + cl_git_pass(git_odb_foreach(_odb, foreach_cb, NULL)); + cl_assert_equal_i(43 + 1640, nobj); /* count + in-pack */ +} + +void test_odb_foreach__one_pack(void) +{ + git_odb_backend *backend = NULL; + + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); + cl_git_pass(git_odb_add_backend(_odb, backend, 1)); + _repo = NULL; + + nobj = 0; + cl_git_pass(git_odb_foreach(_odb, foreach_cb, NULL)); + cl_assert(nobj == 1628); +} + +static int foreach_stop_cb(git_oid *oid, void *data) +{ + GIT_UNUSED(data); + GIT_UNUSED(oid); + + nobj++; + + return (nobj == 1000); +} + +void test_odb_foreach__interrupt_foreach(void) +{ + nobj = 0; + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + git_repository_odb(&_odb, _repo); + + cl_assert_equal_i(GIT_EUSER, git_odb_foreach(_odb, foreach_stop_cb, NULL)); + cl_assert(nobj == 1000); +} diff --git a/tests-clar/odb/pack_data_one.h b/tests-clar/odb/pack_data_one.h new file mode 100644 index 00000000000..13570ba78cf --- /dev/null +++ b/tests-clar/odb/pack_data_one.h @@ -0,0 +1,19 @@ +/* Just a few to make sure it's working, the rest is tested already */ +static const char *packed_objects_one[] = { + "9fcf811e00fa469688943a9152c16d4ee90fb9a9", + "a93f42a5b5e9de40fa645a9ff1e276a021c9542b", + "12bf5f3e3470d90db177ccf1b5e8126409377fc6", + "ed1ea164cdbe3c4b200fb4fa19861ea90eaee222", + "dfae6ed8f6dd8acc3b40a31811ea316239223559", + "aefe66d192771201e369fde830530f4475beec30", + "775e4b4c1296e9e3104f2a36ca9cf9356a130959", + "412ec4e4a6a7419bc1be00561fe474e54cb499fe", + "236e7579fed7763be77209efb8708960982f3cb3", + "09fe9364461cf60dd1c46b0e9545b1e47bb1a297", + "d76d8a6390d1cf32138d98a91b1eb7e0275a12f5", + "d0fdf2dcff2f548952eec536ccc6d266550041bc", + "a20d733a9fa79fa5b4cbb9639864f93325ec27a6", + "785d3fe8e7db5ade2c2242fecd46c32a7f4dc59f", + "4d8d0fd9cb6045075385701c3f933ec13345e9c4", + "0cfd861bd547b6520d1fc2e190e8359e0a9c9b90" +}; diff --git a/tests-clar/odb/packed_one.c b/tests-clar/odb/packed_one.c new file mode 100644 index 00000000000..a064829ddc7 --- /dev/null +++ b/tests-clar/odb/packed_one.c @@ -0,0 +1,58 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "pack_data_one.h" +#include "pack.h" + +static git_odb *_odb; + +void test_odb_packed_one__initialize(void) +{ + git_odb_backend *backend = NULL; + + cl_git_pass(git_odb_new(&_odb)); + cl_git_pass(git_odb_backend_one_pack(&backend, cl_fixture("testrepo.git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx"))); + cl_git_pass(git_odb_add_backend(_odb, backend, 1)); +} + +void test_odb_packed_one__cleanup(void) +{ + git_odb_free(_odb); +} + +void test_odb_packed_one__mass_read(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { + git_oid id; + git_odb_object *obj; + + cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); + cl_assert(git_odb_exists(_odb, &id) == 1); + cl_git_pass(git_odb_read(&obj, _odb, &id)); + + git_odb_object_free(obj); + } +} + +void test_odb_packed_one__read_header_0(void) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(packed_objects_one); ++i) { + git_oid id; + git_odb_object *obj; + size_t len; + git_otype type; + + cl_git_pass(git_oid_fromstr(&id, packed_objects_one[i])); + + cl_git_pass(git_odb_read(&obj, _odb, &id)); + cl_git_pass(git_odb_read_header(&len, &type, _odb, &id)); + + cl_assert(obj->raw.len == len); + cl_assert(obj->raw.type == type); + + git_odb_object_free(obj); + } +} diff --git a/tests-clar/refs/branches/create.c b/tests-clar/refs/branches/create.c index ad7e1fd2c7b..fe72d4708e8 100644 --- a/tests-clar/refs/branches/create.c +++ b/tests-clar/refs/branches/create.c @@ -1,19 +1,22 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; -static git_oid branch_target_oid; static git_object *target; +static git_reference *branch; void test_refs_branches_create__initialize(void) { cl_fixture_sandbox("testrepo.git"); cl_git_pass(git_repository_open(&repo, "testrepo.git")); + + branch = NULL; } void test_refs_branches_create__cleanup(void) { + git_reference_free(branch); + git_object_free(target); git_repository_free(repo); @@ -39,54 +42,24 @@ void test_refs_branches_create__can_create_a_local_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); -} - -void test_refs_branches_create__creating_a_local_branch_triggers_the_creation_of_a_new_direct_reference(void) -{ - git_reference *branch; - - retrieve_known_commit(&target, repo); - - cl_git_fail(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); - - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - - cl_git_pass(git_reference_lookup(&branch, repo, GIT_REFS_HEADS_DIR NEW_BRANCH_NAME)); - cl_assert(git_reference_type(branch) == GIT_REF_OID); - - git_reference_free(branch); + cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); } void test_refs_branches_create__can_not_create_a_branch_if_its_name_collide_with_an_existing_one(void) { retrieve_known_commit(&target, repo); - cl_git_fail(git_branch_create(&branch_target_oid, repo, "br2", target, 0)); + cl_git_fail(git_branch_create(&branch, repo, "br2", target, 0)); } void test_refs_branches_create__can_force_create_over_an_existing_branch(void) { retrieve_known_commit(&target, repo); - cl_git_pass(git_branch_create(&branch_target_oid, repo, "br2", target, 1)); - cl_git_pass(git_oid_cmp(&branch_target_oid, git_object_id(target))); -} - -void test_refs_branches_create__can_not_create_a_branch_pointing_at_an_object_unknown_from_the_repository(void) -{ - git_repository *repo2; - - /* Open another instance of the same repository */ - cl_git_pass(git_repository_open(&repo2, cl_fixture("testrepo.git"))); - - /* Retrieve a commit object from this different repository */ - retrieve_known_commit(&target, repo2); - - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - - git_repository_free(repo2); + cl_git_pass(git_branch_create(&branch, repo, "br2", target, 1)); + cl_git_pass(git_oid_cmp(git_reference_oid(branch), git_object_id(target))); + cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); } void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_it_to_its_commit(void) @@ -94,8 +67,8 @@ void test_refs_branches_create__creating_a_branch_targeting_a_tag_dereferences_i /* b25fa35 is a tag, pointing to another tag which points to a commit */ retrieve_target_from_oid(&target, repo, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - cl_git_pass(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); - cl_git_pass(git_oid_streq(&branch_target_oid, "e90810b8df3e80c413d903f631643c716887138d")); + cl_git_pass(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_pass(git_oid_streq(git_reference_oid(branch), "e90810b8df3e80c413d903f631643c716887138d")); } void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit_object(void) @@ -103,11 +76,11 @@ void test_refs_branches_create__can_not_create_a_branch_pointing_to_a_non_commit /* 53fc32d is the tree of commit e90810b */ retrieve_target_from_oid(&target, repo, "53fc32d17276939fc79ed05badaef2db09990016"); - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); git_object_free(target); /* 521d87c is an annotated tag pointing to a blob */ retrieve_target_from_oid(&target, repo, "521d87c1ec3aef9824daf6d96cc0ae3710766d91"); - cl_git_fail(git_branch_create(&branch_target_oid, repo, NEW_BRANCH_NAME, target, 0)); + cl_git_fail(git_branch_create(&branch, repo, NEW_BRANCH_NAME, target, 0)); } diff --git a/tests-clar/refs/branches/delete.c b/tests-clar/refs/branches/delete.c index 03d3c56d7fa..b261240cd6b 100644 --- a/tests-clar/refs/branches/delete.c +++ b/tests-clar/refs/branches/delete.c @@ -1,6 +1,5 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; static git_reference *fake_remote; @@ -24,37 +23,37 @@ void test_refs_branches_delete__cleanup(void) cl_fixture_cleanup("testrepo.git"); } -void test_refs_branches_delete__can_not_delete_a_non_existing_branch(void) -{ - cl_git_fail(git_branch_delete(repo, "i-am-not-a-local-branch", GIT_BRANCH_LOCAL)); - cl_git_fail(git_branch_delete(repo, "neither/a-remote-one", GIT_BRANCH_REMOTE)); -} - void test_refs_branches_delete__can_not_delete_a_branch_pointed_at_by_HEAD(void) { git_reference *head; + git_reference *branch; /* Ensure HEAD targets the local master branch */ cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); cl_assert(strcmp("refs/heads/master", git_reference_target(head)) == 0); git_reference_free(head); - cl_git_fail(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_not_delete_a_branch_if_HEAD_is_missing(void) { git_reference *head; + git_reference *branch = NULL; cl_git_pass(git_reference_lookup(&head, repo, GIT_HEAD_FILE)); git_reference_delete(head); - cl_git_fail(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_fail(git_branch_delete(branch)); + git_reference_free(branch); } void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD(void) { - git_reference *master, *head; + git_reference *master, *head, *branch; /* Detach HEAD and make it target the commit that "master" points to */ cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); @@ -62,30 +61,21 @@ void test_refs_branches_delete__can_delete_a_branch_pointed_at_by_detached_HEAD( git_reference_free(head); git_reference_free(master); - cl_git_pass(git_branch_delete(repo, "master", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); } void test_refs_branches_delete__can_delete_a_local_branch(void) { - cl_git_pass(git_branch_delete(repo, "br2", GIT_BRANCH_LOCAL)); + git_reference *branch; + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); + cl_git_pass(git_branch_delete(branch)); } void test_refs_branches_delete__can_delete_a_remote_branch(void) { - cl_git_pass(git_branch_delete(repo, "nulltoken/master", GIT_BRANCH_REMOTE)); + git_reference *branch; + cl_git_pass(git_branch_lookup(&branch, repo, "nulltoken/master", GIT_BRANCH_REMOTE)); + cl_git_pass(git_branch_delete(branch)); } -static void assert_non_exisitng_branch_removal(const char *branch_name, git_branch_t branch_type) -{ - int error; - error = git_branch_delete(repo, branch_name, branch_type); - - cl_git_fail(error); - cl_assert_equal_i(GIT_ENOTFOUND, error); -} - -void test_refs_branches_delete__deleting_a_non_existing_branch_returns_ENOTFOUND(void) -{ - assert_non_exisitng_branch_removal("i-do-not-locally-exist", GIT_BRANCH_LOCAL); - assert_non_exisitng_branch_removal("neither/remotely", GIT_BRANCH_REMOTE); -} diff --git a/tests-clar/refs/branches/foreach.c b/tests-clar/refs/branches/foreach.c index 51e3381f873..92d5b1f651e 100644 --- a/tests-clar/refs/branches/foreach.c +++ b/tests-clar/refs/branches/foreach.c @@ -1,6 +1,5 @@ #include "clar_libgit2.h" #include "refs.h" -#include "branch.h" static git_repository *repo; static git_reference *fake_remote; @@ -48,7 +47,7 @@ static void assert_retrieval(unsigned int flags, unsigned int expected_count) void test_refs_branches_foreach__retrieve_all_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 9); + assert_retrieval(GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, 14); } void test_refs_branches_foreach__retrieve_remote_branches(void) @@ -58,7 +57,7 @@ void test_refs_branches_foreach__retrieve_remote_branches(void) void test_refs_branches_foreach__retrieve_local_branches(void) { - assert_retrieval(GIT_BRANCH_LOCAL, 7); + assert_retrieval(GIT_BRANCH_LOCAL, 12); } struct expectations { @@ -126,3 +125,28 @@ void test_refs_branches_foreach__retrieve_remote_symbolic_HEAD_when_present(void assert_branch_has_been_found(exp, "nulltoken/HEAD"); assert_branch_has_been_found(exp, "nulltoken/HEAD"); } + +static int branch_list_interrupt_cb( + const char *branch_name, git_branch_t branch_type, void *payload) +{ + int *count; + + GIT_UNUSED(branch_type); + GIT_UNUSED(branch_name); + + count = (int *)payload; + (*count)++; + + return (*count == 5); +} + +void test_refs_branches_foreach__can_cancel(void) +{ + int count = 0; + + cl_assert_equal_i(GIT_EUSER, + git_branch_foreach(repo, GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, + branch_list_interrupt_cb, &count)); + + cl_assert_equal_i(5, count); +} diff --git a/tests-clar/refs/branches/lookup.c b/tests-clar/refs/branches/lookup.c new file mode 100644 index 00000000000..2aabf9889a1 --- /dev/null +++ b/tests-clar/refs/branches/lookup.c @@ -0,0 +1,35 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_lookup__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; +} + +void test_refs_branches_lookup__cleanup(void) +{ + git_reference_free(branch); + + git_repository_free(repo); +} + +void test_refs_branches_lookup__can_retrieve_a_local_branch(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "br2", GIT_BRANCH_LOCAL)); +} + +void test_refs_branches_lookup__can_retrieve_a_remote_tracking_branch(void) +{ + cl_git_pass(git_branch_lookup(&branch, repo, "test/master", GIT_BRANCH_REMOTE)); +} + +void test_refs_branches_lookup__trying_to_retrieve_an_unknown_branch_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "where/are/you", GIT_BRANCH_LOCAL)); + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_lookup(&branch, repo, "over/here", GIT_BRANCH_REMOTE)); +} diff --git a/tests-clar/refs/branches/move.c b/tests-clar/refs/branches/move.c index 242e5cd0187..6750473e1e9 100644 --- a/tests-clar/refs/branches/move.c +++ b/tests-clar/refs/branches/move.c @@ -1,72 +1,64 @@ #include "clar_libgit2.h" -#include "branch.h" +#include "refs.h" static git_repository *repo; +static git_reference *ref; void test_refs_branches_move__initialize(void) { - cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_repository_open(&repo, "testrepo.git")); + repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_reference_lookup(&ref, repo, "refs/heads/br2")); } void test_refs_branches_move__cleanup(void) { - git_repository_free(repo); - - cl_fixture_cleanup("testrepo.git"); + git_reference_free(ref); + cl_git_sandbox_cleanup(); } #define NEW_BRANCH_NAME "new-branch-on-the-block" void test_refs_branches_move__can_move_a_local_branch(void) { - cl_git_pass(git_branch_move(repo, "br2", NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, NEW_BRANCH_NAME, 0)); + cl_assert_equal_s(GIT_REFS_HEADS_DIR NEW_BRANCH_NAME, git_reference_name(ref)); } void test_refs_branches_move__can_move_a_local_branch_to_a_different_namespace(void) { /* Downward */ - cl_git_pass(git_branch_move(repo, "br2", "somewhere/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, "somewhere/" NEW_BRANCH_NAME, 0)); /* Upward */ - cl_git_pass(git_branch_move(repo, "somewhere/" NEW_BRANCH_NAME, "br2", 0)); + cl_git_pass(git_branch_move(ref, "br2", 0)); } void test_refs_branches_move__can_move_a_local_branch_to_a_partially_colliding_namespace(void) { /* Downward */ - cl_git_pass(git_branch_move(repo, "br2", "br2/" NEW_BRANCH_NAME, 0)); + cl_git_pass(git_branch_move(ref, "br2/" NEW_BRANCH_NAME, 0)); /* Upward */ - cl_git_pass(git_branch_move(repo, "br2/" NEW_BRANCH_NAME, "br2", 0)); + cl_git_pass(git_branch_move(ref, "br2", 0)); } void test_refs_branches_move__can_not_move_a_branch_if_its_destination_name_collide_with_an_existing_one(void) { - cl_git_fail(git_branch_move(repo, "br2", "master", 0)); + cl_git_fail(git_branch_move(ref, "master", 0)); } -void test_refs_branches_move__can_not_move_a_non_existing_branch(void) +void test_refs_branches_move__can_not_move_a_non_branch(void) { - cl_git_fail(git_branch_move(repo, "i-am-no-branch", NEW_BRANCH_NAME, 0)); -} + git_reference *tag; -void test_refs_branches_move__can_force_move_over_an_existing_branch(void) -{ - cl_git_pass(git_branch_move(repo, "br2", "master", 1)); -} + cl_git_pass(git_reference_lookup(&tag, repo, "refs/tags/e90810b")); + cl_git_fail(git_branch_move(tag, NEW_BRANCH_NAME, 0)); -void test_refs_branches_move__can_not_move_a_branch_through_its_canonical_name(void) -{ - cl_git_fail(git_branch_move(repo, "refs/heads/br2", NEW_BRANCH_NAME, 1)); + git_reference_free(tag); } -void test_refs_branches_move__moving_a_non_exisiting_branch_returns_ENOTFOUND(void) +void test_refs_branches_move__can_force_move_over_an_existing_branch(void) { - int error; - - error = git_branch_move(repo, "where/am/I", NEW_BRANCH_NAME, 0); - cl_git_fail(error); - - cl_assert_equal_i(GIT_ENOTFOUND, error); + cl_git_pass(git_branch_move(ref, "master", 1)); } diff --git a/tests-clar/refs/branches/tracking.c b/tests-clar/refs/branches/tracking.c new file mode 100644 index 00000000000..9cf435e8839 --- /dev/null +++ b/tests-clar/refs/branches/tracking.c @@ -0,0 +1,80 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *repo; +static git_reference *branch; + +void test_refs_branches_tracking__initialize(void) +{ + cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); + + branch = NULL; +} + +void test_refs_branches_tracking__cleanup(void) +{ + git_reference_free(branch); + + git_repository_free(repo); +} + +void test_refs_branches_tracking__can_retrieve_the_remote_tracking_reference_of_a_local_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/master")); + + cl_git_pass(git_branch_tracking(&tracking, branch)); + + cl_assert_equal_s("refs/remotes/test/master", git_reference_name(tracking)); + + git_reference_free(branch); + git_reference_free(tracking); +} + +void test_refs_branches_tracking__can_retrieve_the_local_tracking_reference_of_a_local_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/track-local")); + + cl_git_pass(git_branch_tracking(&tracking, branch)); + + cl_assert_equal_s("refs/heads/master", git_reference_name(tracking)); + + git_reference_free(branch); + git_reference_free(tracking); +} + +void test_refs_branches_tracking__cannot_retrieve_a_remote_tracking_reference_from_a_non_branch(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/tags/e90810b")); + + cl_git_fail(git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} + +void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_plain_local_branch_returns_GIT_ENOTFOUND(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/subtrees")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} + +void test_refs_branches_tracking__trying_to_retrieve_a_remote_tracking_reference_from_a_branch_with_no_fetchspec_returns_GIT_ENOTFOUND(void) +{ + git_reference *branch, *tracking; + + cl_git_pass(git_reference_lookup(&branch, repo, "refs/heads/cannot-fetch")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_branch_tracking(&tracking, branch)); + + git_reference_free(branch); +} diff --git a/tests-clar/refs/create.c b/tests-clar/refs/create.c index dde4c57454b..2e42cb6076a 100644 --- a/tests-clar/refs/create.c +++ b/tests-clar/refs/create.c @@ -4,7 +4,7 @@ #include "git2/reflog.h" #include "reflog.h" -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +static const char *current_master_tip = "099fabac3a9ea935598528c27f866e34089c2eff"; static const char *current_head_target = "refs/heads/master"; static git_repository *g_repo; diff --git a/tests-clar/refs/foreachglob.c b/tests-clar/refs/foreachglob.c index 8bbcd71eded..12134293335 100644 --- a/tests-clar/refs/foreachglob.c +++ b/tests-clar/refs/foreachglob.c @@ -45,8 +45,8 @@ static void assert_retrieval(const char *glob, unsigned int flags, int expected_ void test_refs_foreachglob__retrieve_all_refs(void) { - /* 7 heads (including one packed head) + 1 note + 2 remotes + 6 tags */ - assert_retrieval("*", GIT_REF_LISTALL, 16); + /* 8 heads (including one packed head) + 1 note + 2 remotes + 6 tags */ + assert_retrieval("*", GIT_REF_LISTALL, 21); } void test_refs_foreachglob__retrieve_remote_branches(void) @@ -56,7 +56,7 @@ void test_refs_foreachglob__retrieve_remote_branches(void) void test_refs_foreachglob__retrieve_local_branches(void) { - assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 7); + assert_retrieval("refs/heads/*", GIT_REF_LISTALL, 12); } void test_refs_foreachglob__retrieve_partially_named_references(void) @@ -68,3 +68,25 @@ void test_refs_foreachglob__retrieve_partially_named_references(void) assert_retrieval("*test*", GIT_REF_LISTALL, 4); } + + +static int interrupt_cb(const char *reference_name, void *payload) +{ + int *count = (int *)payload; + + GIT_UNUSED(reference_name); + + (*count)++; + + return (*count == 11); +} + +void test_refs_foreachglob__can_cancel(void) +{ + int count = 0; + + cl_assert_equal_i(GIT_EUSER, git_reference_foreach_glob( + repo, "*", GIT_REF_LISTALL, interrupt_cb, &count) ); + + cl_assert_equal_i(11, count); +} diff --git a/tests-clar/refs/list.c b/tests-clar/refs/list.c index 2a7b157cae9..2daa3941e0e 100644 --- a/tests-clar/refs/list.c +++ b/tests-clar/refs/list.c @@ -36,7 +36,7 @@ void test_refs_list__all(void) /* We have exactly 9 refs in total if we include the packed ones: * there is a reference that exists both in the packfile and as * loose, but we only list it once */ - cl_assert(ref_list.count == 9); + cl_assert_equal_i((int)ref_list.count, 10); git_strarray_free(&ref_list); } @@ -51,3 +51,18 @@ void test_refs_list__symbolic_only(void) git_strarray_free(&ref_list); } + +void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_extension(void) +{ + git_strarray ref_list; + + /* Create a fake locked reference */ + cl_git_mkfile( + "./testrepo/.git/refs/heads/hanwen.lock", + "144344043ba4d4a405da03de3844aa829ae8be0e\n"); + + cl_git_pass(git_reference_list(&ref_list, g_repo, GIT_REF_LISTALL)); + cl_assert_equal_i((int)ref_list.count, 10); + + git_strarray_free(&ref_list); +} diff --git a/tests-clar/refs/normalize.c b/tests-clar/refs/normalize.c index 135d0a9b632..4e80e4b0ba4 100644 --- a/tests-clar/refs/normalize.c +++ b/tests-clar/refs/normalize.c @@ -4,70 +4,111 @@ #include "git2/reflog.h" #include "reflog.h" - // Helpers -static void ensure_refname_normalized(int is_oid_ref, +static void ensure_refname_normalized(unsigned int flags, const char *input_refname, const char *expected_refname) { char buffer_out[GIT_REFNAME_MAX]; - if (is_oid_ref) - cl_git_pass(git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname)); - else - cl_git_pass(git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname)); + cl_git_pass(git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); - if (expected_refname) - cl_assert(0 == strcmp(buffer_out, expected_refname)); + cl_assert_equal_i(0, strcmp(buffer_out, expected_refname)); } -static void ensure_refname_invalid(int is_oid_ref, const char *input_refname) +static void ensure_refname_invalid(unsigned int flags, const char *input_refname) { char buffer_out[GIT_REFNAME_MAX]; - if (is_oid_ref) - cl_git_fail(git_reference__normalize_name_oid(buffer_out, sizeof(buffer_out), input_refname)); - else - cl_git_fail(git_reference__normalize_name(buffer_out, sizeof(buffer_out), input_refname)); + cl_git_fail(git_reference_normalize_name(buffer_out, sizeof(buffer_out), input_refname, flags)); } -#define OID_REF 1 -#define SYM_REF 0 - +void test_refs_normalize__can_normalize_a_direct_reference_name(void) +{ + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/dummy/a", "refs/dummy/a"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/stash", "refs/stash"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/tags/a", "refs/tags/a"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/heads/a/b", "refs/heads/a/b"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/heads/a./b", "refs/heads/a./b"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "/refs///heads///a", "refs/heads/a"); +} +void test_refs_normalize__can_normalize_some_specific_one_level_direct_reference_names(void) +{ + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "HEAD", "HEAD"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "MERGE_HEAD", "MERGE_HEAD"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "FETCH_HEAD", "FETCH_HEAD"); +} -void test_refs_normalize__direct(void) +void test_refs_normalize__cannot_normalize_any_direct_reference_name(void) { - // normalize a direct (OID) reference name - ensure_refname_invalid(OID_REF, "a"); - ensure_refname_invalid(OID_REF, ""); - ensure_refname_invalid(OID_REF, "refs/heads/a/"); - ensure_refname_invalid(OID_REF, "refs/heads/a."); - ensure_refname_invalid(OID_REF, "refs/heads/a.lock"); - ensure_refname_normalized(OID_REF, "refs/dummy/a", NULL); - ensure_refname_normalized(OID_REF, "refs/stash", NULL); - ensure_refname_normalized(OID_REF, "refs/tags/a", "refs/tags/a"); - ensure_refname_normalized(OID_REF, "refs/heads/a/b", "refs/heads/a/b"); - ensure_refname_normalized(OID_REF, "refs/heads/a./b", "refs/heads/a./b"); - ensure_refname_invalid(OID_REF, "refs/heads/foo?bar"); - ensure_refname_invalid(OID_REF, "refs/heads\foo"); - ensure_refname_normalized(OID_REF, "refs/heads/v@ation", "refs/heads/v@ation"); - ensure_refname_normalized(OID_REF, "refs///heads///a", "refs/heads/a"); - ensure_refname_invalid(OID_REF, "refs/heads/.a/b"); - ensure_refname_invalid(OID_REF, "refs/heads/foo/../bar"); - ensure_refname_invalid(OID_REF, "refs/heads/foo..bar"); - ensure_refname_invalid(OID_REF, "refs/heads/./foo"); - ensure_refname_invalid(OID_REF, "refs/heads/v@{ation"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "a"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "/a"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "//a"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, ""); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/a/"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/a."); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/a.lock"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/foo?bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads\foo"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs/heads/v@ation", "refs/heads/v@ation"); + ensure_refname_normalized( + GIT_REF_FORMAT_NORMAL, "refs///heads///a", "refs/heads/a"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/.a/b"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/foo/../bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/foo..bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/./foo"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "refs/heads/v@{ation"); } void test_refs_normalize__symbolic(void) { - // normalize a symbolic reference name - ensure_refname_normalized(SYM_REF, "a", "a"); - ensure_refname_normalized(SYM_REF, "a/b", "a/b"); - ensure_refname_normalized(SYM_REF, "refs///heads///a", "refs/heads/a"); - ensure_refname_invalid(SYM_REF, ""); - ensure_refname_invalid(SYM_REF, "heads\foo"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, ""); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "heads\foo"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "///"); + + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "a", "a"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "a/b", "a/b"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs///heads///a", "refs/heads/a"); + + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "HEAD", "HEAD"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "MERGE_HEAD", "MERGE_HEAD"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "FETCH_HEAD", "FETCH_HEAD"); } /* Ported from JGit, BSD licence. @@ -77,31 +118,42 @@ void test_refs_normalize__jgit_suite(void) // tests borrowed from JGit /* EmptyString */ - ensure_refname_invalid(SYM_REF, ""); - ensure_refname_invalid(SYM_REF, "/"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, ""); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "/"); /* MustHaveTwoComponents */ - ensure_refname_invalid(OID_REF, "master"); - ensure_refname_normalized(SYM_REF, "heads/master", "heads/master"); + ensure_refname_invalid( + GIT_REF_FORMAT_NORMAL, "master"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "heads/master", "heads/master"); /* ValidHead */ - ensure_refname_normalized(SYM_REF, "refs/heads/master", "refs/heads/master"); - ensure_refname_normalized(SYM_REF, "refs/heads/pu", "refs/heads/pu"); - ensure_refname_normalized(SYM_REF, "refs/heads/z", "refs/heads/z"); - ensure_refname_normalized(SYM_REF, "refs/heads/FoO", "refs/heads/FoO"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master", "refs/heads/master"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/pu", "refs/heads/pu"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/z", "refs/heads/z"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/FoO", "refs/heads/FoO"); /* ValidTag */ - ensure_refname_normalized(SYM_REF, "refs/tags/v1.0", "refs/tags/v1.0"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/tags/v1.0", "refs/tags/v1.0"); /* NoLockSuffix */ - ensure_refname_invalid(SYM_REF, "refs/heads/master.lock"); + ensure_refname_invalid(GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master.lock"); /* NoDirectorySuffix */ - ensure_refname_invalid(SYM_REF, "refs/heads/master/"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master/"); /* NoSpace */ - ensure_refname_invalid(SYM_REF, "refs/heads/i haz space"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/i haz space"); /* NoAsciiControlCharacters */ { @@ -112,89 +164,153 @@ void test_refs_normalize__jgit_suite(void) strncpy(buffer + 15, (const char *)&c, 1); strncpy(buffer + 16, "er", 2); buffer[18 - 1] = '\0'; - ensure_refname_invalid(SYM_REF, buffer); + ensure_refname_invalid(GIT_REF_FORMAT_ALLOW_ONELEVEL, buffer); } } /* NoBareDot */ - ensure_refname_invalid(SYM_REF, "refs/heads/."); - ensure_refname_invalid(SYM_REF, "refs/heads/.."); - ensure_refname_invalid(SYM_REF, "refs/heads/./master"); - ensure_refname_invalid(SYM_REF, "refs/heads/../master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/."); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/.."); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/./master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/../master"); /* NoLeadingOrTrailingDot */ - ensure_refname_invalid(SYM_REF, "."); - ensure_refname_invalid(SYM_REF, "refs/heads/.bar"); - ensure_refname_invalid(SYM_REF, "refs/heads/..bar"); - ensure_refname_invalid(SYM_REF, "refs/heads/bar."); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "."); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/.bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/..bar"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/bar."); /* ContainsDot */ - ensure_refname_normalized(SYM_REF, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); - ensure_refname_invalid(SYM_REF, "refs/heads/master..pu"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/m.a.s.t.e.r", "refs/heads/m.a.s.t.e.r"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master..pu"); /* NoMagicRefCharacters */ - ensure_refname_invalid(SYM_REF, "refs/heads/master^"); - ensure_refname_invalid(SYM_REF, "refs/heads/^master"); - ensure_refname_invalid(SYM_REF, "^refs/heads/master"); - - ensure_refname_invalid(SYM_REF, "refs/heads/master~"); - ensure_refname_invalid(SYM_REF, "refs/heads/~master"); - ensure_refname_invalid(SYM_REF, "~refs/heads/master"); - - ensure_refname_invalid(SYM_REF, "refs/heads/master:"); - ensure_refname_invalid(SYM_REF, "refs/heads/:master"); - ensure_refname_invalid(SYM_REF, ":refs/heads/master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master^"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/^master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "^refs/heads/master"); + + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master~"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/~master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "~refs/heads/master"); + + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master:"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/:master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, ":refs/heads/master"); /* ShellGlob */ - ensure_refname_invalid(SYM_REF, "refs/heads/master?"); - ensure_refname_invalid(SYM_REF, "refs/heads/?master"); - ensure_refname_invalid(SYM_REF, "?refs/heads/master"); - - ensure_refname_invalid(SYM_REF, "refs/heads/master["); - ensure_refname_invalid(SYM_REF, "refs/heads/[master"); - ensure_refname_invalid(SYM_REF, "[refs/heads/master"); - - ensure_refname_invalid(SYM_REF, "refs/heads/master*"); - ensure_refname_invalid(SYM_REF, "refs/heads/*master"); - ensure_refname_invalid(SYM_REF, "*refs/heads/master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master?"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/?master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "?refs/heads/master"); + + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master["); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/[master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "[refs/heads/master"); + + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master*"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/*master"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "*refs/heads/master"); /* ValidSpecialCharacters */ - ensure_refname_normalized(SYM_REF, "refs/heads/!", "refs/heads/!"); - ensure_refname_normalized(SYM_REF, "refs/heads/\"", "refs/heads/\""); - ensure_refname_normalized(SYM_REF, "refs/heads/#", "refs/heads/#"); - ensure_refname_normalized(SYM_REF, "refs/heads/$", "refs/heads/$"); - ensure_refname_normalized(SYM_REF, "refs/heads/%", "refs/heads/%"); - ensure_refname_normalized(SYM_REF, "refs/heads/&", "refs/heads/&"); - ensure_refname_normalized(SYM_REF, "refs/heads/'", "refs/heads/'"); - ensure_refname_normalized(SYM_REF, "refs/heads/(", "refs/heads/("); - ensure_refname_normalized(SYM_REF, "refs/heads/)", "refs/heads/)"); - ensure_refname_normalized(SYM_REF, "refs/heads/+", "refs/heads/+"); - ensure_refname_normalized(SYM_REF, "refs/heads/,", "refs/heads/,"); - ensure_refname_normalized(SYM_REF, "refs/heads/-", "refs/heads/-"); - ensure_refname_normalized(SYM_REF, "refs/heads/;", "refs/heads/;"); - ensure_refname_normalized(SYM_REF, "refs/heads/<", "refs/heads/<"); - ensure_refname_normalized(SYM_REF, "refs/heads/=", "refs/heads/="); - ensure_refname_normalized(SYM_REF, "refs/heads/>", "refs/heads/>"); - ensure_refname_normalized(SYM_REF, "refs/heads/@", "refs/heads/@"); - ensure_refname_normalized(SYM_REF, "refs/heads/]", "refs/heads/]"); - ensure_refname_normalized(SYM_REF, "refs/heads/_", "refs/heads/_"); - ensure_refname_normalized(SYM_REF, "refs/heads/`", "refs/heads/`"); - ensure_refname_normalized(SYM_REF, "refs/heads/{", "refs/heads/{"); - ensure_refname_normalized(SYM_REF, "refs/heads/|", "refs/heads/|"); - ensure_refname_normalized(SYM_REF, "refs/heads/}", "refs/heads/}"); + ensure_refname_normalized + (GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/!", "refs/heads/!"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\"", "refs/heads/\""); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/#", "refs/heads/#"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/$", "refs/heads/$"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/%", "refs/heads/%"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/&", "refs/heads/&"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/'", "refs/heads/'"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/(", "refs/heads/("); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/)", "refs/heads/)"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/+", "refs/heads/+"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/,", "refs/heads/,"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/-", "refs/heads/-"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/;", "refs/heads/;"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/<", "refs/heads/<"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/=", "refs/heads/="); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/>", "refs/heads/>"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/@", "refs/heads/@"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/]", "refs/heads/]"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/_", "refs/heads/_"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/`", "refs/heads/`"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/{", "refs/heads/{"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/|", "refs/heads/|"); + ensure_refname_normalized( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/}", "refs/heads/}"); // This is valid on UNIX, but not on Windows // hence we make in invalid due to non-portability // - ensure_refname_invalid(SYM_REF, "refs/heads/\\"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\\"); /* UnicodeNames */ /* * Currently this fails. - * ensure_refname_normalized(SYM_REF, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"); + * ensure_refname_normalized(GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/\u00e5ngstr\u00f6m", "refs/heads/\u00e5ngstr\u00f6m"); */ /* RefLogQueryIsValidRef */ - ensure_refname_invalid(SYM_REF, "refs/heads/master@{1}"); - ensure_refname_invalid(SYM_REF, "refs/heads/master@{1.hour.ago}"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1}"); + ensure_refname_invalid( + GIT_REF_FORMAT_ALLOW_ONELEVEL, "refs/heads/master@{1.hour.ago}"); +} + +void test_refs_normalize__buffer_has_to_be_big_enough_to_hold_the_normalized_version(void) +{ + char buffer_out[21]; + + cl_git_pass(git_reference_normalize_name( + buffer_out, 21, "//refs//heads/long///name", GIT_REF_FORMAT_NORMAL)); + cl_git_fail(git_reference_normalize_name( + buffer_out, 20, "//refs//heads/long///name", GIT_REF_FORMAT_NORMAL)); } diff --git a/tests-clar/refs/peel.c b/tests-clar/refs/peel.c new file mode 100644 index 00000000000..35a290b2e15 --- /dev/null +++ b/tests-clar/refs/peel.c @@ -0,0 +1,91 @@ +#include "clar_libgit2.h" + +static git_repository *g_repo; + +void test_refs_peel__initialize(void) +{ + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); +} + +void test_refs_peel__cleanup(void) +{ + git_repository_free(g_repo); +} + +static void assert_peel( + const char *ref_name, + git_otype requested_type, + const char* expected_sha, + git_otype expected_type) +{ + git_oid expected_oid; + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + + cl_git_pass(git_reference_peel(&peeled, ref, requested_type)); + + cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); + cl_assert_equal_i(0, git_oid_cmp(&expected_oid, git_object_id(peeled))); + + cl_assert_equal_i(expected_type, git_object_type(peeled)); + + git_object_free(peeled); + git_reference_free(ref); +} + +static void assert_peel_error(int error, const char *ref_name, git_otype requested_type) +{ + git_reference *ref; + git_object *peeled; + + cl_git_pass(git_reference_lookup(&ref, g_repo, ref_name)); + + cl_assert_equal_i(error, git_reference_peel(&peeled, ref, requested_type)); + + git_reference_free(ref); +} + +void test_refs_peel__can_peel_a_tag(void) +{ + assert_peel("refs/tags/test", GIT_OBJ_TAG, + "b25fa35b38051e4ae45d4222e795f9df2e43f1d1", GIT_OBJ_TAG); + assert_peel("refs/tags/test", GIT_OBJ_COMMIT, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); + assert_peel("refs/tags/test", GIT_OBJ_TREE, + "53fc32d17276939fc79ed05badaef2db09990016", GIT_OBJ_TREE); + assert_peel("refs/tags/point_to_blob", GIT_OBJ_BLOB, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB); +} + +void test_refs_peel__can_peel_a_branch(void) +{ + assert_peel("refs/heads/master", GIT_OBJ_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("refs/heads/master", GIT_OBJ_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE); +} + +void test_refs_peel__can_peel_a_symbolic_reference(void) +{ + assert_peel("HEAD", GIT_OBJ_COMMIT, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("HEAD", GIT_OBJ_TREE, + "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162", GIT_OBJ_TREE); +} + +void test_refs_peel__cannot_peel_into_a_non_existing_target(void) +{ + assert_peel_error(GIT_ERROR, "refs/tags/point_to_blob", GIT_OBJ_TAG); +} + +void test_refs_peel__can_peel_into_any_non_tag_object(void) +{ + assert_peel("refs/heads/master", GIT_OBJ_ANY, + "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", GIT_OBJ_COMMIT); + assert_peel("refs/tags/point_to_blob", GIT_OBJ_ANY, + "1385f264afb75a56a5bec74243be9b367ba4ca08", GIT_OBJ_BLOB); + assert_peel("refs/tags/test", GIT_OBJ_ANY, + "e90810b8df3e80c413d903f631643c716887138d", GIT_OBJ_COMMIT); +} diff --git a/tests-clar/refs/read.c b/tests-clar/refs/read.c index d7111b23244..f33658754bb 100644 --- a/tests-clar/refs/read.c +++ b/tests-clar/refs/read.c @@ -16,12 +16,12 @@ static git_repository *g_repo; void test_refs_read__initialize(void) { - g_repo = cl_git_sandbox_init("testrepo"); + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); } void test_refs_read__cleanup(void) { - cl_git_sandbox_cleanup(); + git_repository_free(g_repo); } void test_refs_read__loose_tag(void) @@ -192,3 +192,53 @@ void test_refs_read__loose_first(void) git_reference_free(reference); } + +void test_refs_read__chomped(void) +{ + git_reference *test, *chomped; + + cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); + cl_git_pass(git_reference_lookup(&chomped, g_repo, "refs/heads/chomped")); + cl_git_pass(git_oid_cmp(git_reference_oid(test), git_reference_oid(chomped))); + + git_reference_free(test); + git_reference_free(chomped); +} + +void test_refs_read__trailing(void) +{ + git_reference *test, *trailing; + + cl_git_pass(git_reference_lookup(&test, g_repo, "refs/heads/test")); + cl_git_pass(git_reference_lookup(&trailing, g_repo, "refs/heads/trailing")); + cl_git_pass(git_oid_cmp(git_reference_oid(test), git_reference_oid(trailing))); + + git_reference_free(test); + git_reference_free(trailing); +} + +void test_refs_read__unfound_return_ENOTFOUND(void) +{ + git_reference *reference; + + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/master")); + cl_assert_equal_i(GIT_ENOTFOUND, git_reference_lookup(&reference, g_repo, "refs/tags/test/farther/master")); +} + +static void assert_is_branch(const char *name, bool expected_branchness) +{ + git_reference *reference; + cl_git_pass(git_reference_lookup(&reference, g_repo, name)); + cl_assert_equal_i(expected_branchness, git_reference_is_branch(reference)); + git_reference_free(reference); +} + +void test_refs_read__can_determine_if_a_reference_is_a_local_branch(void) +{ + assert_is_branch("refs/heads/master", true); + assert_is_branch("refs/heads/packed", true); + assert_is_branch("refs/remotes/test/master", false); + assert_is_branch("refs/tags/e90810b", false); +} diff --git a/tests-clar/refs/reflog.c b/tests-clar/refs/reflog.c deleted file mode 100644 index 1bc51b2b880..00000000000 --- a/tests-clar/refs/reflog.c +++ /dev/null @@ -1,123 +0,0 @@ -#include "clar_libgit2.h" - -#include "repository.h" -#include "git2/reflog.h" -#include "reflog.h" - - -static const char *new_ref = "refs/heads/test-reflog"; -static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; -static const char *commit_msg = "commit: bla bla"; - -static git_repository *g_repo; - - -// helpers -static void assert_signature(git_signature *expected, git_signature *actual) -{ - cl_assert(actual); - cl_assert_equal_s(expected->name, actual->name); - cl_assert_equal_s(expected->email, actual->email); - cl_assert(expected->when.offset == actual->when.offset); - cl_assert(expected->when.time == actual->when.time); -} - - -// Fixture setup and teardown -void test_refs_reflog__initialize(void) -{ - g_repo = cl_git_sandbox_init("testrepo"); -} - -void test_refs_reflog__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - - -void test_refs_reflog__write_then_read(void) -{ - // write a reflog for a given reference and ensure it can be read back - git_repository *repo2; - git_reference *ref, *lookedup_ref; - git_oid oid; - git_signature *committer; - git_reflog *reflog; - git_reflog_entry *entry; - char oid_str[GIT_OID_HEXSZ+1]; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&oid, current_master_tip); - cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); - - cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - - cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); - cl_git_fail(git_reflog_write(ref, NULL, committer, "no ancestor NULL for an existing reflog")); - cl_git_fail(git_reflog_write(ref, NULL, committer, "no\nnewline")); - cl_git_pass(git_reflog_write(ref, &oid, committer, commit_msg)); - - /* Reopen a new instance of the repository */ - cl_git_pass(git_repository_open(&repo2, "testrepo")); - - /* Lookup the preivously created branch */ - cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); - - /* Read and parse the reflog for this branch */ - cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); - cl_assert(reflog->entries.length == 2); - - entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 0); - assert_signature(committer, entry->committer); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); - cl_assert_equal_s("0000000000000000000000000000000000000000", oid_str); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); - cl_assert_equal_s(current_master_tip, oid_str); - cl_assert(entry->msg == NULL); - - entry = (git_reflog_entry *)git_vector_get(&reflog->entries, 1); - assert_signature(committer, entry->committer); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_old); - cl_assert_equal_s(current_master_tip, oid_str); - git_oid_tostr(oid_str, GIT_OID_HEXSZ+1, &entry->oid_cur); - cl_assert_equal_s(current_master_tip, oid_str); - cl_assert_equal_s(commit_msg, entry->msg); - - git_signature_free(committer); - git_reflog_free(reflog); - git_repository_free(repo2); - - git_reference_free(ref); - git_reference_free(lookedup_ref); -} - -void test_refs_reflog__dont_write_bad(void) -{ - // avoid writing an obviously wrong reflog - git_reference *ref; - git_oid oid; - git_signature *committer; - - /* Create a new branch pointing at the HEAD */ - git_oid_fromstr(&oid, current_master_tip); - cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); - git_reference_free(ref); - cl_git_pass(git_reference_lookup(&ref, g_repo, new_ref)); - - cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); - - /* Write the reflog for the new branch */ - cl_git_pass(git_reflog_write(ref, NULL, committer, NULL)); - - /* Try to update the reflog with wrong information: - * It's no new reference, so the ancestor OID cannot - * be NULL. */ - cl_git_fail(git_reflog_write(ref, NULL, committer, NULL)); - - git_signature_free(committer); - - git_reference_free(ref); -} diff --git a/tests-clar/refs/reflog/drop.c b/tests-clar/refs/reflog/drop.c new file mode 100644 index 00000000000..86781c0411b --- /dev/null +++ b/tests-clar/refs/reflog/drop.c @@ -0,0 +1,130 @@ +#include "clar_libgit2.h" + +#include "reflog.h" + +static git_repository *g_repo; +static git_reflog *g_reflog; +static unsigned int entrycount; + +void test_refs_reflog_drop__initialize(void) +{ + git_reference *ref; + + g_repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD")); + + git_reflog_read(&g_reflog, ref); + entrycount = git_reflog_entrycount(g_reflog); + + git_reference_free(ref); +} + +void test_refs_reflog_drop__cleanup(void) +{ + git_reflog_free(g_reflog); + + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_drop__dropping_a_non_exisiting_entry_from_the_log_returns_ENOTFOUND(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_reflog_drop(g_reflog, entrycount, 0)); + + cl_assert_equal_i(entrycount, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_an_entry(void) +{ + cl_assert(entrycount > 4); + + cl_git_pass(git_reflog_drop(g_reflog, 2, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +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; + + 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); + + cl_git_pass(git_reflog_drop(g_reflog, 2, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + after_next = git_reflog_entry_byindex(g_reflog, 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)); +} + +void test_refs_reflog_drop__can_drop_the_first_entry(void) +{ + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, 0, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_drop_the_last_entry(void) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 0)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + 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) +{ + const git_reflog_entry *entry; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + + entry = git_reflog_entry_byindex(g_reflog, entrycount - 2); + cl_assert(git_oid_streq(&entry->oid_old, GIT_OID_HEX_ZERO) == 0); +} + +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)); + + cl_assert_equal_i(0, git_reflog_entrycount(g_reflog)); +} + +void test_refs_reflog_drop__can_persist_deletion_on_disk(void) +{ + git_reference *ref; + + cl_assert(entrycount > 2); + + cl_git_pass(git_reference_lookup(&ref, g_repo, g_reflog->ref_name)); + cl_git_pass(git_reflog_drop(g_reflog, entrycount - 1, 1)); + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); + cl_git_pass(git_reflog_write(g_reflog)); + + git_reflog_free(g_reflog); + + git_reflog_read(&g_reflog, ref); + git_reference_free(ref); + + cl_assert_equal_i(entrycount - 1, git_reflog_entrycount(g_reflog)); +} diff --git a/tests-clar/refs/reflog/reflog.c b/tests-clar/refs/reflog/reflog.c new file mode 100644 index 00000000000..20f08f523cb --- /dev/null +++ b/tests-clar/refs/reflog/reflog.c @@ -0,0 +1,172 @@ +#include "clar_libgit2.h" + +#include "repository.h" +#include "git2/reflog.h" +#include "reflog.h" + + +static const char *new_ref = "refs/heads/test-reflog"; +static const char *current_master_tip = "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"; +#define commit_msg "commit: bla bla" + +static git_repository *g_repo; + + +// helpers +static void assert_signature(git_signature *expected, git_signature *actual) +{ + cl_assert(actual); + cl_assert_equal_s(expected->name, actual->name); + cl_assert_equal_s(expected->email, actual->email); + cl_assert(expected->when.offset == actual->when.offset); + cl_assert(expected->when.time == actual->when.time); +} + + +// Fixture setup and teardown +void test_refs_reflog_reflog__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_reflog_reflog__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_reflog_reflog__append_then_read(void) +{ + // write a reflog for a given reference and ensure it can be read back + git_repository *repo2; + git_reference *ref, *lookedup_ref; + git_oid oid; + git_signature *committer; + git_reflog *reflog; + const git_reflog_entry *entry; + + /* Create a new branch pointing at the HEAD */ + git_oid_fromstr(&oid, current_master_tip); + cl_git_pass(git_reference_create_oid(&ref, g_repo, new_ref, &oid, 0)); + + cl_git_pass(git_signature_now(&committer, "foo", "foo@bar")); + + cl_git_pass(git_reflog_read(&reflog, ref)); + + cl_git_fail(git_reflog_append(reflog, &oid, committer, "no inner\nnewline")); + cl_git_pass(git_reflog_append(reflog, &oid, committer, NULL)); + cl_git_pass(git_reflog_append(reflog, &oid, committer, commit_msg "\n")); + cl_git_pass(git_reflog_write(reflog)); + git_reflog_free(reflog); + + /* Reopen a new instance of the repository */ + cl_git_pass(git_repository_open(&repo2, "testrepo.git")); + + /* Lookup the previously created branch */ + cl_git_pass(git_reference_lookup(&lookedup_ref, repo2, new_ref)); + + /* Read and parse the reflog for this branch */ + cl_git_pass(git_reflog_read(&reflog, lookedup_ref)); + cl_assert_equal_i(2, git_reflog_entrycount(reflog)); + + entry = git_reflog_entry_byindex(reflog, 0); + 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); + assert_signature(committer, entry->committer); + cl_assert(git_oid_cmp(&oid, &entry->oid_old) == 0); + cl_assert(git_oid_cmp(&oid, &entry->oid_cur) == 0); + cl_assert_equal_s(commit_msg, entry->msg); + + git_signature_free(committer); + git_reflog_free(reflog); + git_repository_free(repo2); + + git_reference_free(ref); + git_reference_free(lookedup_ref); +} + +void test_refs_reflog_reflog__renaming_the_reference_moves_the_reflog(void) +{ + git_reference *master; + git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; + + git_buf_joinpath(&master_log_path, git_repository_path(g_repo), GIT_REFLOG_DIR); + git_buf_puts(&moved_log_path, git_buf_cstr(&master_log_path)); + git_buf_joinpath(&master_log_path, git_buf_cstr(&master_log_path), "refs/heads/master"); + git_buf_joinpath(&moved_log_path, git_buf_cstr(&moved_log_path), "refs/moved"); + + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&master_log_path))); + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&moved_log_path))); + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(master, "refs/moved", 0)); + + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&master_log_path))); + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&moved_log_path))); + + git_reference_free(master); + git_buf_free(&moved_log_path); + git_buf_free(&master_log_path); +} + +static void assert_has_reflog(bool expected_result, const char *name) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, g_repo, name)); + + cl_assert_equal_i(expected_result, git_reference_has_log(ref)); + + git_reference_free(ref); +} + +void test_refs_reflog_reflog__reference_has_reflog(void) +{ + assert_has_reflog(true, "HEAD"); + assert_has_reflog(true, "refs/heads/master"); + assert_has_reflog(false, "refs/heads/subtrees"); +} + +void test_refs_reflog_reflog__reading_the_reflog_from_a_reference_with_no_log_returns_an_empty_one(void) +{ + git_reference *subtrees; + git_reflog *reflog; + git_buf subtrees_log_path = GIT_BUF_INIT; + + cl_git_pass(git_reference_lookup(&subtrees, g_repo, "refs/heads/subtrees")); + + git_buf_join_n(&subtrees_log_path, '/', 3, git_repository_path(g_repo), GIT_REFLOG_DIR, git_reference_name(subtrees)); + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&subtrees_log_path))); + + cl_git_pass(git_reflog_read(&reflog, subtrees)); + + cl_assert_equal_i(0, git_reflog_entrycount(reflog)); + + git_reflog_free(reflog); + git_reference_free(subtrees); + git_buf_free(&subtrees_log_path); +} + +void test_refs_reflog_reflog__cannot_write_a_moved_reflog(void) +{ + git_reference *master; + git_buf master_log_path = GIT_BUF_INIT, moved_log_path = GIT_BUF_INIT; + git_reflog *reflog; + + cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/master")); + cl_git_pass(git_reflog_read(&reflog, master)); + + cl_git_pass(git_reflog_write(reflog)); + + cl_git_pass(git_reference_rename(master, "refs/moved", 0)); + + cl_git_fail(git_reflog_write(reflog)); + + git_reflog_free(reflog); + git_reference_free(master); + git_buf_free(&moved_log_path); + git_buf_free(&master_log_path); +} diff --git a/tests-clar/refs/revparse.c b/tests-clar/refs/revparse.c index c71e6d844d5..14bd9fb8413 100644 --- a/tests-clar/refs/revparse.c +++ b/tests-clar/refs/revparse.c @@ -1,6 +1,9 @@ #include "clar_libgit2.h" #include "git2/revparse.h" +#include "buffer.h" +#include "refs.h" +#include "path.h" static git_repository *g_repo; static git_object *g_obj; @@ -9,18 +12,28 @@ static char g_orig_tz[16] = {0}; /* Helpers */ -static void test_object(const char *spec, const char *expected_oid) +static void test_object_inrepo(const char *spec, const char *expected_oid, git_repository *repo) { char objstr[64] = {0}; + git_object *obj = NULL; + int error; - cl_git_pass(git_revparse_single(&g_obj, g_repo, spec)); - git_oid_fmt(objstr, git_object_id(g_obj)); - cl_assert_equal_s(objstr, expected_oid); + error = git_revparse_single(&obj, repo, spec); - git_object_free(g_obj); - g_obj = NULL; + if (expected_oid != NULL) { + cl_assert_equal_i(0, error); + git_oid_fmt(objstr, git_object_id(obj)); + cl_assert_equal_s(objstr, expected_oid); + } else + cl_assert_equal_i(GIT_ENOTFOUND, error); + + git_object_free(obj); } +static void test_object(const char *spec, const char *expected_oid) +{ + test_object_inrepo(spec, expected_oid, g_repo); +} void test_refs_revparse__initialize(void) { @@ -28,22 +41,31 @@ void test_refs_revparse__initialize(void) if (tz) strcpy(g_orig_tz, tz); cl_setenv("TZ", "UTC"); - g_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_repository_open(&g_repo, cl_fixture("testrepo.git"))); } void test_refs_revparse__cleanup(void) { - cl_git_sandbox_cleanup(); + git_repository_free(g_repo); g_obj = NULL; cl_setenv("TZ", g_orig_tz); } - void test_refs_revparse__nonexistant_object(void) { - cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist")); - cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist^1")); - cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't exist~2")); + test_object("this-does-not-exist", NULL); + test_object("this-does-not-exist^1", NULL); + test_object("this-does-not-exist~2", NULL); +} + +void test_refs_revparse__invalid_reference_name(void) +{ + cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense^1")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "this doesn't make sense~2")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "")); + } void test_refs_revparse__shas(void) @@ -55,6 +77,9 @@ void test_refs_revparse__shas(void) void test_refs_revparse__head(void) { test_object("HEAD", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD^0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); } void test_refs_revparse__full_refs(void) @@ -79,64 +104,169 @@ void test_refs_revparse__describe_output(void) void test_refs_revparse__nth_parent(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^-1")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "^")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "be3563a^{tree}^")); + test_object("be3563a^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("be3563a^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("be3563a^1^1", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + test_object("be3563a^^", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); test_object("be3563a^2^1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("be3563a^0", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("be3563a^{commit}^", "9fd738e8f7967c078dceed8190330fc8648ee56a"); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, "be3563a^42")); + test_object("be3563a^42", NULL); } void test_refs_revparse__not_tag(void) { test_object("point_to_blob^{}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); test_object("wrapped_tag^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master^{tree}^{}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + test_object("e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("tags/e90810b^{}", "e90810b8df3e80c413d903f631643c716887138d"); + test_object("e908^{}", "e90810b8df3e80c413d903f631643c716887138d"); } void test_refs_revparse__to_type(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{trip}")); + test_object("wrapped_tag^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("wrapped_tag^{tree}", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); test_object("point_to_blob^{blob}", "1385f264afb75a56a5bec74243be9b367ba4ca08"); - - cl_git_fail(git_revparse_single(&g_obj, g_repo, "wrapped_tag^{blob}")); + test_object("master^{commit}^{commit}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); } void test_refs_revparse__linear_history(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "~")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "foo~bar")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~bar")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~-1")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master~0bar")); test_object("master~0", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("master~1", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("master~2", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1~1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("master~~", "9fd738e8f7967c078dceed8190330fc8648ee56a"); } void test_refs_revparse__chaining(void) { + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{0}@{0}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{u}@{-1}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1}@{-1}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-3}@{0}")); + + test_object("master@{0}~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); + test_object("@{u}@{0}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("@{-1}@{0}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); + test_object("@{-4}@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); test_object("master~1^1", "9fd738e8f7967c078dceed8190330fc8648ee56a"); test_object("master~1^2", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object("master^1^2~1", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + test_object("master^^2^", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); test_object("master^1^1^1^1^1", "8496071c1b46c854b31185ea97743be6a8774479"); + test_object("master^^1^2^1", NULL); +} + +void test_refs_revparse__upstream(void) +{ + cl_git_fail(git_revparse_single(&g_obj, g_repo, "e90810b@{u}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "refs/tags/e90810b@{u}")); + + test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("refs/heads/master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); } -void test_refs_revparse__reflog(void) +void test_refs_revparse__ordinal(void) +{ + cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{-2}")); + + /* TODO: make the test below actually fail + * cl_git_fail(git_revparse_single(&g_obj, g_repo, "master@{1a}")); + */ + + test_object("nope@{0}", NULL); + test_object("master@{31415}", NULL); + test_object("@{1000}", NULL); + test_object("@{2}", NULL); + + test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("refs/heads/master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); +} + +void test_refs_revparse__previous_head(void) { cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-xyz}")); cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-0}")); - cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{1000}")); + cl_git_fail(git_revparse_single(&g_obj, g_repo, "@{-1b}")); + + test_object("@{-42}", NULL); test_object("@{-2}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); test_object("@{-1}", "a4a7dce85cf63874e984719f4fdd239f5145052f"); - test_object("master@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("@{1}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{upstream}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{u}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); +} + +static void create_fake_stash_reference_and_reflog(git_repository *repo) +{ + git_reference *master; + git_buf log_path = GIT_BUF_INIT; + + git_buf_joinpath(&log_path, git_repository_path(repo), "logs/refs/fakestash"); + + cl_assert_equal_i(false, git_path_isfile(git_buf_cstr(&log_path))); + + cl_git_pass(git_reference_lookup(&master, repo, "refs/heads/master")); + cl_git_pass(git_reference_rename(master, "refs/fakestash", 0)); + + cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&log_path))); + + git_buf_free(&log_path); + git_reference_free(master); +} + +void test_refs_revparse__reflog_of_a_ref_under_refs(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo.git"); + + test_object_inrepo("refs/fakestash", NULL, repo); + + create_fake_stash_reference_and_reflog(repo); + + /* + * $ git reflog -1 refs/fakestash + * a65fedf refs/fakestash@{0}: commit: checking in + * + * $ git reflog -1 refs/fakestash@{0} + * a65fedf refs/fakestash@{0}: commit: checking in + * + * $ git reflog -1 fakestash + * a65fedf fakestash@{0}: commit: checking in + * + * $ git reflog -1 fakestash@{0} + * a65fedf fakestash@{0}: commit: checking in + */ + test_object_inrepo("refs/fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("refs/fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("fakestash", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + test_object_inrepo("fakestash@{0}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750", repo); + + cl_git_sandbox_cleanup(); } void test_refs_revparse__revwalk(void) @@ -153,14 +283,73 @@ void test_refs_revparse__revwalk(void) void test_refs_revparse__date(void) { - test_object("HEAD@{10 years ago}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master@{2012-4-30 10:23:20 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - test_object("master@{2012-4-30 18:24 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); - test_object("master@{2012-4-30 23:24 -0300}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + /* + * $ git reflog HEAD --date=iso + * a65fedf HEAD@{2012-04-30 08:23:41 -0900}: checkout: moving from br2 to master + * a4a7dce HEAD@{2012-04-30 08:23:37 -0900}: commit: checking in + * c47800c HEAD@{2012-04-30 08:23:28 -0900}: checkout: moving from master to br2 + * a65fedf HEAD@{2012-04-30 08:23:23 -0900}: commit: + * be3563a HEAD@{2012-04-30 10:22:43 -0700}: clone: from /Users/ben/src/libgit2/tes + * + * $ git reflog HEAD --date=raw + * a65fedf HEAD@{1335806621 -0900}: checkout: moving from br2 to master + * a4a7dce HEAD@{1335806617 -0900}: commit: checking in + * c47800c HEAD@{1335806608 -0900}: checkout: moving from master to br2 + * a65fedf HEAD@{1335806603 -0900}: commit: + * be3563a HEAD@{1335806563 -0700}: clone: from /Users/ben/src/libgit2/tests/resour + */ + test_object("HEAD@{10 years ago}", NULL); - /* Core git gives a65fedf, because they don't take time zones into account. */ - test_object("master@{1335806640}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("HEAD@{1 second}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD@{1 second ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("HEAD@{2 days ago}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog master --date=iso + * a65fedf master@{2012-04-30 09:23:23 -0800}: commit: checking in + * be3563a master@{2012-04-30 09:22:43 -0800}: clone: from /Users/ben/src... + * + * $ git reflog master --date=raw + * a65fedf master@{1335806603 -0800}: commit: checking in + * be3563a master@{1335806563 -0800}: clone: from /Users/ben/src/libgit2/tests/reso + */ + + + /* + * $ git reflog -1 "master@{2012-04-30 17:22:42 +0000}" + * warning: Log for 'master' only goes back to Mon, 30 Apr 2012 09:22:43 -0800. + */ + test_object("master@{2012-04-30 17:22:42 +0000}", NULL); + test_object("master@{2012-04-30 09:22:42 -0800}", NULL); + + /* + * $ git reflog -1 "master@{2012-04-30 17:22:43 +0000}" + * be3563a master@{Mon Apr 30 09:22:43 2012 -0800}: clone: from /Users/ben/src/libg + */ + test_object("master@{2012-04-30 17:22:43 +0000}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + test_object("master@{2012-04-30 09:22:43 -0800}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + + /* + * $ git reflog -1 "master@{2012-4-30 09:23:27 -0800}" + * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in + */ + test_object("master@{2012-4-30 09:23:27 -0800}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog -1 master@{2012-05-03} + * a65fedf master@{Mon Apr 30 09:23:23 2012 -0800}: commit: checking in + */ + test_object("master@{2012-05-03}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + + /* + * $ git reflog -1 "master@{1335806603}" + * a65fedf + * + * $ git reflog -1 "master@{1335806602}" + * be3563a + */ + test_object("master@{1335806603}", "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + test_object("master@{1335806602}", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); } void test_refs_revparse__colon(void) @@ -168,18 +357,32 @@ void test_refs_revparse__colon(void) cl_git_fail(git_revparse_single(&g_obj, g_repo, ":/")); cl_git_fail(git_revparse_single(&g_obj, g_repo, ":2:README")); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, ":/not found in any commit")); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, "subtrees:ab/42.txt")); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, "subtrees:ab/4.txt/nope")); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, "subtrees:nope")); - cl_assert_equal_i(GIT_ENOTFOUND, git_revparse_single(&g_obj, g_repo, "test/master^1:branch_file.txt")); + test_object(":/not found in any commit", NULL); + test_object("subtrees:ab/42.txt", NULL); + test_object("subtrees:ab/4.txt/nope", NULL); + test_object("subtrees:nope", NULL); + test_object("test/master^1:branch_file.txt", NULL); + + /* From tags */ + test_object("test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("tags/test:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); + test_object("tags/e90810b:readme.txt", "0266163a49e280c4f5ed1e08facd36a2bd716bcf"); - /* Trees */ + /* From commits */ + test_object("a65f:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + + /* From trees */ + test_object("a65f^{tree}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + test_object("944c:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); + + /* Retrieving trees */ test_object("master:", "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); test_object("subtrees:", "ae90f12eea699729ed24555e40b9fd669da12a12"); test_object("subtrees:ab", "f1425cef211cc08caa31e7b545ffb232acb098c3"); + test_object("subtrees:ab/", "f1425cef211cc08caa31e7b545ffb232acb098c3"); - /* Blobs */ + /* Retrieving blobs */ test_object("subtrees:ab/4.txt", "d6c93164c249c8000205dd4ec5cbca1b516d487f"); test_object("subtrees:ab/de/fgh/1.txt", "1f67fc4386b2d171e0d21be1c447e12660561f9b"); test_object("master:README", "a8233120f6ad708f843d861ce2b7228ec4e3dec6"); @@ -188,4 +391,63 @@ void test_refs_revparse__colon(void) test_object(":/one", "c47800c7266a2be04c571c04d5a6614691ea99bd"); test_object(":/packed commit t", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9"); test_object("test/master^2:branch_file.txt", "45b983be36b73c0788dc9cbcb76cbb80fc7bb057"); + test_object("test/master@{1}:branch_file.txt", "3697d64be941a53d4ae8f6a271e4e3fa56b022cc"); +} + +void test_refs_revparse__disambiguation(void) +{ + /* + * $ git show e90810b + * tag e90810b + * Tagger: Vicent Marti + * Date: Thu Aug 12 03:59:17 2010 +0200 + * + * This is a very simple tag. + * + * commit e90810b8df3e80c413d903f631643c716887138d + * Author: Vicent Marti + * Date: Thu Aug 5 18:42:20 2010 +0200 + * + * Test commit 2 + * + * diff --git a/readme.txt b/readme.txt + * index 6336846..0266163 100644 + * --- a/readme.txt + * +++ b/readme.txt + * @@ -1 +1,2 @@ + * Testing a readme.txt + * +Now we add a single line here + * + * $ git show-ref e90810b + * 7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b + * + */ + test_object("e90810b", "7b4384978d2493e851f9cca7858815fac9b10980"); + + /* + * $ git show e90810 + * commit e90810b8df3e80c413d903f631643c716887138d + * Author: Vicent Marti + * Date: Thu Aug 5 18:42:20 2010 +0200 + * + * Test commit 2 + * + * diff --git a/readme.txt b/readme.txt + * index 6336846..0266163 100644 + * --- a/readme.txt + * +++ b/readme.txt + * @@ -1 +1,2 @@ + * Testing a readme.txt + * +Now we add a single line here + */ + test_object("e90810", "e90810b8df3e80c413d903f631643c716887138d"); +} + +void test_refs_revparse__a_too_short_objectid_returns_EAMBIGUOUS(void) +{ + int result; + + result = git_revparse_single(&g_obj, g_repo, "e90"); + + cl_assert_equal_i(GIT_EAMBIGUOUS, result); } diff --git a/tests-clar/repo/getters.c b/tests-clar/repo/getters.c index 966de1f168d..ffcd171f277 100644 --- a/tests-clar/repo/getters.c +++ b/tests-clar/repo/getters.c @@ -23,52 +23,6 @@ void test_repo_getters__empty(void) git_repository_free(repo_empty); } -void test_repo_getters__head_detached(void) -{ - git_repository *repo; - git_reference *ref; - git_oid oid; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_detached(repo) == 0); - - /* detach the HEAD */ - git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); - cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); - cl_assert(git_repository_head_detached(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_detached(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - -void test_repo_getters__head_orphan(void) -{ - git_repository *repo; - git_reference *ref; - - cl_git_pass(git_repository_open(&repo, "testrepo.git")); - - cl_assert(git_repository_head_orphan(repo) == 0); - - /* orphan HEAD */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); - cl_assert(git_repository_head_orphan(repo) == 1); - git_reference_free(ref); - - /* take the reop back to it's original state */ - cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); - cl_assert(git_repository_head_orphan(repo) == 0); - - git_reference_free(ref); - git_repository_free(repo); -} - void test_repo_getters__retrieving_the_odb_honors_the_refcount(void) { git_odb *odb; diff --git a/tests-clar/repo/hashfile.c b/tests-clar/repo/hashfile.c new file mode 100644 index 00000000000..129e5d371b9 --- /dev/null +++ b/tests-clar/repo/hashfile.c @@ -0,0 +1,88 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static git_repository *_repo; + +void test_repo_hashfile__initialize(void) +{ + _repo = cl_git_sandbox_init("status"); +} + +void test_repo_hashfile__cleanup(void) +{ + cl_git_sandbox_cleanup(); + _repo = NULL; +} + +void test_repo_hashfile__simple(void) +{ + git_oid a, b; + git_buf full = GIT_BUF_INIT; + + /* hash with repo relative path */ + cl_git_pass(git_odb_hashfile(&a, "status/current_file", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "current_file", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + + cl_git_pass(git_buf_joinpath(&full, git_repository_workdir(_repo), "current_file")); + + /* hash with full path */ + cl_git_pass(git_odb_hashfile(&a, full.ptr, GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + + /* hash with invalid type */ + cl_git_fail(git_odb_hashfile(&a, full.ptr, GIT_OBJ_ANY)); + cl_git_fail(git_repository_hashfile(&b, _repo, full.ptr, GIT_OBJ_OFS_DELTA, NULL)); + + git_buf_free(&full); +} + +void test_repo_hashfile__filtered(void) +{ + git_oid a, b; + git_config *config; + + cl_git_pass(git_repository_config(&config, _repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + git_config_free(config); + + cl_git_append2file("status/.gitattributes", "*.txt text\n*.bin binary\n\n"); + + /* create some sample content with CRLF in it */ + cl_git_mkfile("status/testfile.txt", "content\r\n"); + cl_git_mkfile("status/testfile.bin", "other\r\nstuff\r\n"); + + /* not equal hashes because of filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes because filter is binary */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, NULL)); + cl_assert(git_oid_equal(&a, &b)); + + /* equal hashes when 'as_file' points to binary filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "foo.bin")); + cl_assert(git_oid_equal(&a, &b)); + + /* not equal hashes when 'as_file' points to text filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, "foo.txt")); + cl_assert(git_oid_cmp(&a, &b)); + + /* equal hashes when 'as_file' is empty and turns off filtering */ + cl_git_pass(git_odb_hashfile(&a, "status/testfile.txt", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_BLOB, "")); + cl_assert(git_oid_equal(&a, &b)); + + cl_git_pass(git_odb_hashfile(&a, "status/testfile.bin", GIT_OBJ_BLOB)); + cl_git_pass(git_repository_hashfile(&b, _repo, "testfile.bin", GIT_OBJ_BLOB, "")); + cl_assert(git_oid_equal(&a, &b)); + + /* some hash type failures */ + cl_git_fail(git_odb_hashfile(&a, "status/testfile.txt", 0)); + cl_git_fail(git_repository_hashfile(&b, _repo, "testfile.txt", GIT_OBJ_ANY, NULL)); +} diff --git a/tests-clar/repo/head.c b/tests-clar/repo/head.c new file mode 100644 index 00000000000..64dec69dd8f --- /dev/null +++ b/tests-clar/repo/head.c @@ -0,0 +1,165 @@ +#include "clar_libgit2.h" +#include "refs.h" + +git_repository *repo; + +void test_repo_head__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_head__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_repo_head__head_detached(void) +{ + git_reference *ref; + git_oid oid; + + cl_assert(git_repository_head_detached(repo) == 0); + + /* detach the HEAD */ + git_oid_fromstr(&oid, "c47800c7266a2be04c571c04d5a6614691ea99bd"); + cl_git_pass(git_reference_create_oid(&ref, repo, "HEAD", &oid, 1)); + cl_assert(git_repository_head_detached(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_detached(repo) == 0); + + git_reference_free(ref); +} + +void test_repo_head__head_orphan(void) +{ + git_reference *ref; + + cl_assert(git_repository_head_orphan(repo) == 0); + + /* orphan HEAD */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/orphan", 1)); + cl_assert(git_repository_head_orphan(repo) == 1); + git_reference_free(ref); + + /* take the reop back to it's original state */ + cl_git_pass(git_reference_create_symbolic(&ref, repo, "HEAD", "refs/heads/master", 1)); + cl_assert(git_repository_head_orphan(repo) == 0); + + git_reference_free(ref); +} + +void test_repo_head__set_head_Attaches_HEAD_to_un_unborn_branch_when_the_branch_doesnt_exist(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/doesnt/exist/yet")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_head(&head, repo)); +} + +void test_repo_head__set_head_Returns_ENOTFOUND_when_the_reference_doesnt_exist(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head(repo, "refs/tags/doesnt/exist/yet")); +} + +void test_repo_head__set_head_Fails_when_the_reference_points_to_a_non_commitish(void) +{ + cl_git_fail(git_repository_set_head(repo, "refs/tags/point_to_blob")); +} + +void test_repo_head__set_head_Attaches_HEAD_when_the_reference_points_to_a_branch(void) +{ + git_reference *head; + + cl_git_pass(git_repository_set_head(repo, "refs/heads/br2")); + + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + cl_assert_equal_s("refs/heads/br2", git_reference_name(head)); + + git_reference_free(head); +} + +static void assert_head_is_correctly_detached(void) +{ + git_reference *head; + git_object *commit; + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_head(&head, repo)); + + cl_git_pass(git_object_lookup(&commit, repo, git_reference_oid(head), GIT_OBJ_COMMIT)); + + git_object_free(commit); + git_reference_free(head); +} + +void test_repo_head__set_head_Detaches_HEAD_when_the_reference_doesnt_point_to_a_branch(void) +{ + cl_git_pass(git_repository_set_head(repo, "refs/tags/test")); + + cl_assert_equal_i(true, git_repository_head_detached(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__set_head_detached_Return_ENOTFOUND_when_the_object_doesnt_exist(void) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef")); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_set_head_detached(repo, &oid)); +} + +void test_repo_head__set_head_detached_Fails_when_the_object_isnt_a_commitish(void) +{ + git_object *blob; + + cl_git_pass(git_revparse_single(&blob, repo, "point_to_blob")); + + cl_git_fail(git_repository_set_head_detached(repo, git_object_id(blob))); + + git_object_free(blob); +} + +void test_repo_head__set_head_detached_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + git_object *tag; + + cl_git_pass(git_revparse_single(&tag, repo, "tags/test")); + cl_assert_equal_i(GIT_OBJ_TAG, git_object_type(tag)); + + cl_git_pass(git_repository_set_head_detached(repo, git_object_id(tag))); + + assert_head_is_correctly_detached(); + + git_object_free(tag); +} + +void test_repo_head__detach_head_Detaches_HEAD_and_make_it_point_to_the_peeled_commit(void) +{ + cl_assert_equal_i(false, git_repository_head_detached(repo)); + + cl_git_pass(git_repository_detach_head(repo)); + + assert_head_is_correctly_detached(); +} + +void test_repo_head__detach_head_Fails_if_HEAD_and_point_to_a_non_commitish(void) +{ + git_reference *head; + + cl_git_pass(git_reference_create_symbolic(&head, repo, GIT_HEAD_FILE, "refs/tags/point_to_blob", 1)); + + cl_git_fail(git_repository_detach_head(repo)); + + git_reference_free(head); +} diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 556a22b6ff3..f76e8bc3dc4 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -2,6 +2,7 @@ #include "fileops.h" #include "repository.h" #include "config.h" +#include "path.h" enum repo_mode { STANDARD_REPOSITORY = 0, @@ -29,6 +30,8 @@ static void ensure_repository_init( { const char *workdir; + cl_assert(!git_path_isdir(working_directory)); + cl_git_pass(git_repository_init(&_repo, working_directory, is_bare)); workdir = git_repository_workdir(_repo); @@ -46,7 +49,8 @@ static void ensure_repository_init( #ifdef GIT_WIN32 if (!is_bare) { - cl_assert((GetFileAttributes(git_repository_path(_repo)) & FILE_ATTRIBUTE_HIDDEN) != 0); + DWORD fattrs = GetFileAttributes(git_repository_path(_repo)); + cl_assert((fattrs & FILE_ATTRIBUTE_HIDDEN) != 0); } #endif @@ -83,7 +87,7 @@ void test_repo_init__bare_repo_escaping_current_workdir(void) git_buf path_current_workdir = GIT_BUF_INIT; cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL)); - + cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c")); cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE)); @@ -234,6 +238,7 @@ void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) int current_value; /* Init a new repo */ + cl_set_cleanup(&cleanup_repository, "not.overwrite.git"); cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1)); /* Change the "core.ignorecase" config value to something unlikely */ @@ -241,6 +246,7 @@ void test_repo_init__reinit_doesnot_overwrite_ignorecase(void) git_config_set_int32(config, "core.ignorecase", 42); git_config_free(config); git_repository_free(_repo); + _repo = NULL; /* Reinit the repository */ cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1)); @@ -265,13 +271,16 @@ void test_repo_init__reinit_overwrites_filemode(void) #endif /* Init a new repo */ + cl_set_cleanup(&cleanup_repository, "overwrite.git"); cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1)); + /* Change the "core.filemode" config value to something unlikely */ git_repository_config(&config, _repo); git_config_set_bool(config, "core.filemode", !expected); git_config_free(config); git_repository_free(_repo); + _repo = NULL; /* Reinit the repository */ cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1)); @@ -290,3 +299,97 @@ void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void) git_repository_free(_repo); assert_config_entry_on_init_bytype("core.logallrefupdates", true, false); } + +void test_repo_init__extended_0(void) +{ + git_repository_init_options opts; + memset(&opts, 0, sizeof(opts)); + + /* without MKDIR this should fail */ + cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts)); + + /* make the directory first, then it should succeed */ + cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/")); + cl_assert(!git__suffixcmp(git_repository_path(_repo), "/extended/.git/")); + cl_assert(!git_repository_is_bare(_repo)); + cl_assert(git_repository_is_empty(_repo)); + + cleanup_repository("extended"); +} + +void test_repo_init__extended_1(void) +{ + git_reference *ref; + git_remote *remote; + struct stat st; + git_repository_init_options opts; + memset(&opts, 0, sizeof(opts)); + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR; + opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP; + opts.workdir_path = "../c_wd"; + opts.description = "Awesomest test repository evah"; + opts.initial_head = "development"; + opts.origin_url = "https://github.com/libgit2/libgit2.git"; + + cl_git_pass(git_repository_init_ext(&_repo, "root/b/c.git", &opts)); + + cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/c_wd/")); + cl_assert(!git__suffixcmp(git_repository_path(_repo), "/c.git/")); + cl_assert(git_path_isfile("root/b/c_wd/.git")); + cl_assert(!git_repository_is_bare(_repo)); + /* repo will not be counted as empty because we set head to "development" */ + cl_assert(!git_repository_is_empty(_repo)); + + cl_git_pass(git_path_lstat(git_repository_path(_repo), &st)); + cl_assert(S_ISDIR(st.st_mode)); + cl_assert((S_ISGID & st.st_mode) == S_ISGID); + + cl_git_pass(git_reference_lookup(&ref, _repo, "HEAD")); + cl_assert(git_reference_type(ref) == GIT_REF_SYMBOLIC); + cl_assert_equal_s("refs/heads/development", git_reference_target(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&remote, _repo, "origin")); + cl_assert_equal_s("origin", git_remote_name(remote)); + cl_assert_equal_s(opts.origin_url, git_remote_url(remote)); + git_remote_free(remote); + + git_repository_free(_repo); + cl_fixture_cleanup("root"); +} + +void test_repo_init__extended_with_template(void) +{ + git_repository_init_options opts; + memset(&opts, 0, sizeof(opts)); + + opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE; + opts.template_path = cl_fixture("template"); + + cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts)); + + cl_assert(git_repository_is_bare(_repo)); + cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/")); + + cleanup_repository("templated.git"); +} + +void test_repo_init__can_reinit_an_initialized_repository(void) +{ + git_repository *reinit; + + cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_repository_init(&_repo, "extended", false)); + + cl_git_pass(git_repository_init(&reinit, "extended", false)); + + cl_assert_equal_s(git_repository_path(_repo), git_repository_path(reinit)); + + git_repository_free(reinit); + cleanup_repository("extended"); +} diff --git a/tests-clar/repo/message.c b/tests-clar/repo/message.c new file mode 100644 index 00000000000..4a6f13b9df1 --- /dev/null +++ b/tests-clar/repo/message.c @@ -0,0 +1,47 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refs.h" +#include "posix.h" + +static git_repository *_repo; +static git_buf _path; +static char *_actual; + +void test_repo_message__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_repo_message__cleanup(void) +{ + cl_git_sandbox_cleanup(); + git_buf_free(&_path); + git__free(_actual); + _actual = NULL; +} + +void test_repo_message__none(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(NULL, 0, _repo)); +} + +void test_repo_message__message(void) +{ + const char expected[] = "Test\n\nThis is a test of the emergency broadcast system\n"; + ssize_t len; + + cl_git_pass(git_buf_joinpath(&_path, git_repository_path(_repo), "MERGE_MSG")); + cl_git_mkfile(git_buf_cstr(&_path), expected); + + len = git_repository_message(NULL, 0, _repo); + cl_assert(len > 0); + _actual = git__malloc(len + 1); + cl_assert(_actual != NULL); + + cl_assert(git_repository_message(_actual, len, _repo) > 0); + _actual[len] = '\0'; + cl_assert_equal_s(expected, _actual); + + cl_git_pass(p_unlink(git_buf_cstr(&_path))); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_message(NULL, 0, _repo)); +} diff --git a/tests-clar/repo/setters.c b/tests-clar/repo/setters.c index 6242d8541d2..cd6e389aee0 100644 --- a/tests-clar/repo/setters.c +++ b/tests-clar/repo/setters.c @@ -2,6 +2,8 @@ #include "buffer.h" #include "posix.h" #include "util.h" +#include "path.h" +#include "fileops.h" static git_repository *repo; @@ -16,7 +18,7 @@ void test_repo_setters__cleanup(void) { git_repository_free(repo); cl_fixture_cleanup("testrepo.git"); - cl_must_pass(p_rmdir("new_workdir")); + cl_fixture_cleanup("new_workdir"); } void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standard_one(void) @@ -24,7 +26,7 @@ void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standar cl_assert(git_repository_is_bare(repo) == 1); cl_assert(git_repository_workdir(repo) == NULL); - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); cl_assert(git_repository_workdir(repo) != NULL); cl_assert(git_repository_is_bare(repo) == 0); @@ -32,9 +34,30 @@ void test_repo_setters__setting_a_workdir_turns_a_bare_repository_into_a_standar void test_repo_setters__setting_a_workdir_prettifies_its_path(void) { - cl_git_pass(git_repository_set_workdir(repo, "./new_workdir")); + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", false)); - cl_assert(git__suffixcmp(git_repository_workdir(repo), "/") == 0); + cl_assert(git__suffixcmp(git_repository_workdir(repo), "new_workdir/") == 0); +} + +void test_repo_setters__setting_a_workdir_creates_a_gitlink(void) +{ + git_config *cfg; + const char *val; + git_buf content = GIT_BUF_INIT; + + cl_git_pass(git_repository_set_workdir(repo, "./new_workdir", true)); + + cl_assert(git_path_isfile("./new_workdir/.git")); + + cl_git_pass(git_futils_readbuffer(&content, "./new_workdir/.git")); + cl_assert(git__prefixcmp(git_buf_cstr(&content), "gitdir: ") == 0); + cl_assert(git__suffixcmp(git_buf_cstr(&content), "testrepo.git/") == 0); + git_buf_free(&content); + + cl_git_pass(git_repository_config(&cfg, repo)); + cl_git_pass(git_config_get_string(&val, cfg, "core.worktree")); + cl_assert(git__suffixcmp(val, "new_workdir/") == 0); + git_config_free(cfg); } void test_repo_setters__setting_a_new_index_on_a_repo_which_has_already_loaded_one_properly_honors_the_refcount(void) diff --git a/tests-clar/reset/hard.c b/tests-clar/reset/hard.c new file mode 100644 index 00000000000..ad3badb8a1e --- /dev/null +++ b/tests-clar/reset/hard.c @@ -0,0 +1,46 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "reset_helpers.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static git_object *target; + +void test_reset_hard__initialize(void) +{ + repo = cl_git_sandbox_init("status"); + target = NULL; +} + +void test_reset_hard__cleanup(void) +{ + git_object_free(target); + cl_git_sandbox_cleanup(); +} + +void test_reset_hard__resetting_culls_empty_directories(void) +{ + git_buf subdir_path = GIT_BUF_INIT; + git_buf subfile_path = GIT_BUF_INIT; + git_buf newdir_path = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&newdir_path, git_repository_workdir(repo), "newdir/")); + + 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_git_pass(git_buf_joinpath(&subdir_path, git_repository_workdir(repo), "subdir/")); + cl_assert(git_path_isdir(git_buf_cstr(&subdir_path)) == true); + + 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); + + git_buf_free(&subdir_path); + git_buf_free(&subfile_path); + git_buf_free(&newdir_path); +} diff --git a/tests-clar/reset/mixed.c b/tests-clar/reset/mixed.c index 7cfff65d48e..d5f8e10c5ce 100644 --- a/tests-clar/reset/mixed.c +++ b/tests-clar/reset/mixed.c @@ -27,7 +27,7 @@ void test_reset_mixed__cannot_reset_in_a_bare_repository(void) retrieve_target_from_oid(&target, bare, KNOWN_COMMIT_IN_BARE_REPO); - cl_git_fail(git_reset(bare, target, GIT_RESET_MIXED)); + cl_assert_equal_i(GIT_EBAREREPO, git_reset(bare, target, GIT_RESET_MIXED)); git_repository_free(bare); } diff --git a/tests-clar/resources/attr/.gitted/index b/tests-clar/resources/attr/.gitted/index index 1d60eab8fae..439ffb151ef 100644 Binary files a/tests-clar/resources/attr/.gitted/index and b/tests-clar/resources/attr/.gitted/index differ diff --git a/tests-clar/resources/attr/.gitted/logs/HEAD b/tests-clar/resources/attr/.gitted/logs/HEAD index 73f00f3450c..8ece39f37fd 100644 --- a/tests-clar/resources/attr/.gitted/logs/HEAD +++ b/tests-clar/resources/attr/.gitted/logs/HEAD @@ -6,3 +6,4 @@ a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6 f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer 1328653313 -0800 commit: Some whitespace only changes for testing purposes a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer 1332734901 -0700 commit: added files in sub/sub 217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer 1332735555 -0700 commit: adding more files in sub for tree status +24fa9a9fc4e202313e24b648087495441dab432b 8d0b9df9bd30be7910ddda60548d485bc302b911 yorah 1341230701 +0200 commit: Updating test data so we can test inter-hunk-context diff --git a/tests-clar/resources/attr/.gitted/logs/refs/heads/master b/tests-clar/resources/attr/.gitted/logs/refs/heads/master index 73f00f3450c..8ece39f37fd 100644 --- a/tests-clar/resources/attr/.gitted/logs/refs/heads/master +++ b/tests-clar/resources/attr/.gitted/logs/refs/heads/master @@ -6,3 +6,4 @@ a5d76cad53f66f1312bd995909a5bab3c0820770 370fe9ec224ce33e71f9e5ec2bd1142ce9937a6 f5b0af1fb4f5c0cd7aad880711d368a07333c307 a97cc019851d401a4f1d091cb91a15890a0dd1ba Russell Belfer 1328653313 -0800 commit: Some whitespace only changes for testing purposes a97cc019851d401a4f1d091cb91a15890a0dd1ba 217878ab49e1314388ea2e32dc6fdb58a1b969e0 Russell Belfer 1332734901 -0700 commit: added files in sub/sub 217878ab49e1314388ea2e32dc6fdb58a1b969e0 24fa9a9fc4e202313e24b648087495441dab432b Russell Belfer 1332735555 -0700 commit: adding more files in sub for tree status +24fa9a9fc4e202313e24b648087495441dab432b 8d0b9df9bd30be7910ddda60548d485bc302b911 yorah 1341230701 +0200 commit: Updating test data so we can test inter-hunk-context diff --git a/tests-clar/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 b/tests-clar/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 new file mode 100644 index 00000000000..e49c94acdaa Binary files /dev/null and b/tests-clar/resources/attr/.gitted/objects/16/983da6643656bb44c43965ecb6855c6d574512 differ diff --git a/tests-clar/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 b/tests-clar/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 new file mode 100644 index 00000000000..3dcf088e445 --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/8d/0b9df9bd30be7910ddda60548d485bc302b911 @@ -0,0 +1 @@ +xKj1D)zoli _"hiK2LG!7ȪJ,EPXDS ] /)}/UwR. jp##:?:|;F9܋r=_ )ơN/A[l!q}<Lfx4H\\q֏cjT \ No newline at end of file diff --git a/tests-clar/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b b/tests-clar/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b new file mode 100644 index 00000000000..985c2e2813a --- /dev/null +++ b/tests-clar/resources/attr/.gitted/objects/a0/f7217ae99f5ac3e88534f5cea267febc5fa85b @@ -0,0 +1 @@ +x510 E}?΀;S␈Ԯۓv8O'F:2r)( &޷9ZAѹr9l %3Eo.Vi 1347559804 -0700 commit (initial): initial commit +d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer 1347560491 -0700 commit: some changes diff --git a/tests-clar/resources/diff/.gitted/logs/refs/heads/master b/tests-clar/resources/diff/.gitted/logs/refs/heads/master new file mode 100644 index 00000000000..8c6f6fd18b6 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 d70d245ed97ed2aa596dd1af6536e4bfdb047b69 Russell Belfer 1347559804 -0700 commit (initial): initial commit +d70d245ed97ed2aa596dd1af6536e4bfdb047b69 7a9e0b02e63179929fed24f0a3e0f19168114d10 Russell Belfer 1347560491 -0700 commit: some changes diff --git a/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 b/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 new file mode 100644 index 00000000000..94f9a676def Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/29/ab7053bb4dde0298e03e2c179e890b7dd465a7 differ diff --git a/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 b/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 new file mode 100644 index 00000000000..9fed523dc6c Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/3e/5bcbad2a68e5bc60a53b8388eea53a1a7ab847 differ diff --git a/tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 b/tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 new file mode 100644 index 00000000000..d7df4d6a196 Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/54/6c735f16a3b44d9784075c2c0dab2ac9bf1989 differ diff --git a/tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 b/tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 new file mode 100644 index 00000000000..9bc25eb3495 Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/7a/9e0b02e63179929fed24f0a3e0f19168114d10 differ diff --git a/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 b/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 new file mode 100644 index 00000000000..2fd266be66d Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/7b/808f723a8ca90df319682c221187235af76693 differ diff --git a/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c b/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c new file mode 100644 index 00000000000..7598b591411 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/objects/88/789109439c1e1c3cd45224001edee5304ed53c @@ -0,0 +1 @@ +x+)JMU07g040031QH/H-+(a)[wz {j%;ʊRSrS4W4そN+a \ No newline at end of file diff --git a/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 b/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 new file mode 100644 index 00000000000..86ebe04fed9 Binary files /dev/null and b/tests-clar/resources/diff/.gitted/objects/cb/8294e696339863df760b2ff5d1e275bee72455 differ diff --git a/tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 b/tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 new file mode 100644 index 00000000000..99304c4aa2c --- /dev/null +++ b/tests-clar/resources/diff/.gitted/objects/d7/0d245ed97ed2aa596dd1af6536e4bfdb047b69 @@ -0,0 +1 @@ +x !m_RB:XkVpWp 9{ ,^z#7JygԚA i1Y2VyR)𢒨'm[;-lO_#%v8 \ No newline at end of file diff --git a/tests-clar/resources/diff/.gitted/refs/heads/master b/tests-clar/resources/diff/.gitted/refs/heads/master new file mode 100644 index 00000000000..a83afc38be9 --- /dev/null +++ b/tests-clar/resources/diff/.gitted/refs/heads/master @@ -0,0 +1 @@ +7a9e0b02e63179929fed24f0a3e0f19168114d10 diff --git a/tests-clar/resources/diff/another.txt b/tests-clar/resources/diff/another.txt new file mode 100644 index 00000000000..d0e0bae4d4d --- /dev/null +++ b/tests-clar/resources/diff/another.txt @@ -0,0 +1,38 @@ +Git is fast. With Git, nearly all operations are performed locally, giving +it an huge speed advantage on centralized systems that constantly have to +communicate with a server somewh3r3. + +For testing, large AWS instances were set up in the same availability +zone. Git and SVN were installed on both machines, the Ruby repository was +copied to both Git and SVN servers, and common operations were performed on +both. + +In some cases the commands don't match up exactly. Here, matching on the +lowest common denominator was attempted. For example, the 'commit' tests +also include the time to push for Git, though most of the time you would not +actually be pushing to the server immediately after a commit where the two +commands cannot be separated in SVN. + +Note that this is the best case scenario for SVN - a server with no load +with an 80MB/s bandwidth connection to the client machine. Nearly all of +these times would be even worse for SVN if that connection was slower, while +many of the Git times would not be affected. + +Clearly, in many of these common version control operations, Git is one or +two orders of magnitude faster than SVN, even under ideal conditions for +SVN. + +Let's see how common operations stack up against Subversion, a common +centralized version control system that is similar to CVS or +Perforce. Smaller is faster. + +One place where Git is slower is in the initial clone operation. Here, Git +One place where Git is slower is in the initial clone operation. Here, Git +One place where Git is slower is in the initial clone operation. Here, Git +seen in the above charts, it's not considerably slower for an operation that +is only performed once. + +It's also interesting to note that the size of the data on the client side +is very similar even though Git also has every version of every file for the +entire history of the project. This illustrates how efficient it is at +compressing and storing data on the client side. \ No newline at end of file diff --git a/tests-clar/resources/diff/readme.txt b/tests-clar/resources/diff/readme.txt new file mode 100644 index 00000000000..beedf288d52 --- /dev/null +++ b/tests-clar/resources/diff/readme.txt @@ -0,0 +1,36 @@ +The Git feature that r3ally mak3s it stand apart from n3arly 3v3ry other SCM +out there is its branching model. + +Git allows and encourages you to have multiple local branches that can be +entirely independent of each other. The creation, merging, and deletion of +those lines of development takes seconds. + +Git allows and encourages you to have multiple local branches that can be +entirely independent of each other. The creation, merging, and deletion of +those lines of development takes seconds. + +This means that you can do things like: + +Role-Bas3d Codelin3s. Have a branch that always contains only what goes to +production, another that you merge work into for testing, and several +smaller ones for day to day work. + +Feature Based Workflow. Create new branches for each new feature you're +working on so you can seamlessly switch back and forth between them, then +delete each branch when that feature gets merged into your main line. + +Disposable Experimentation. Create a branch to experiment in, realize it's +not going to work, and just delete it - abandoning the work—with nobody else +ever seeing it (even if you've pushed other branches in the meantime). + +Notably, when you push to a remote repository, you do not have to push all +share it with others. + +Git allows and encourages you to have multiple local branches that can be +entirely independent of each other. The creation, merging, and deletion of +those lines of development takes seconds. + +There are ways to accomplish some of this with other systems, but the work +involved is much more difficult and error-prone. Git makes this process +incredibly easy and it changes the way most developers work when they learn +it.! diff --git a/tests-clar/resources/issue_592/.gitted/index b/tests-clar/resources/issue_592/.gitted/index index eaeb5d76142..be7a29d99fd 100644 Binary files a/tests-clar/resources/issue_592/.gitted/index and b/tests-clar/resources/issue_592/.gitted/index differ diff --git a/tests-clar/resources/status/.gitted/index b/tests-clar/resources/status/.gitted/index index 9a383ec0ccf..2af99a18301 100644 Binary files a/tests-clar/resources/status/.gitted/index and b/tests-clar/resources/status/.gitted/index differ diff --git a/tests-clar/resources/submod2/.gitted/HEAD b/tests-clar/resources/submod2/.gitted/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/config b/tests-clar/resources/submod2/.gitted/config new file mode 100644 index 00000000000..abc420734ca --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/config @@ -0,0 +1,20 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true +[submodule "sm_missing_commits"] + url = ../submod2_target +[submodule "sm_unchanged"] + url = ../submod2_target +[submodule "sm_changed_file"] + url = ../submod2_target +[submodule "sm_changed_index"] + url = ../submod2_target +[submodule "sm_changed_head"] + url = ../submod2_target +[submodule "sm_changed_untracked_file"] + url = ../submod2_target +[submodule "sm_added_and_uncommited"] + url = ../submod2_target diff --git a/tests-clar/resources/submod2/.gitted/description b/tests-clar/resources/submod2/.gitted/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/index b/tests-clar/resources/submod2/.gitted/index new file mode 100644 index 00000000000..0c17e8629df Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/index differ diff --git a/tests-clar/resources/submod2/.gitted/info/exclude b/tests-clar/resources/submod2/.gitted/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.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/submod2/.gitted/logs/HEAD b/tests-clar/resources/submod2/.gitted/logs/HEAD new file mode 100644 index 00000000000..2cf2ca74dd5 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/logs/HEAD @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit +14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule +a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule +5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/tests-clar/resources/submod2/.gitted/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/logs/refs/heads/master new file mode 100644 index 00000000000..2cf2ca74dd5 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit +14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule +a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule +5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/config b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/config new file mode 100644 index 00000000000..2d0583e996b --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_added_and_uncommited + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/description b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/index b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/index new file mode 100644 index 00000000000..65140a51097 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/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/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD new file mode 100644 index 00000000000..53753e7dd7d --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master new file mode 100644 index 00000000000..53753e7dd7d --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..53753e7dd7d --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/config b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/config new file mode 100644 index 00000000000..10cc2508e4f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_file + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/description b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/index b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/index new file mode 100644 index 00000000000..6914a3b6edf Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/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/submod2/.gitted/modules/sm_changed_file/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD new file mode 100644 index 00000000000..e5cb63f8d9b --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master new file mode 100644 index 00000000000..e5cb63f8d9b --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..e5cb63f8d9b --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_file/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG new file mode 100644 index 00000000000..6b8d1e3fce1 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/COMMIT_EDITMSG @@ -0,0 +1 @@ +Making a change in a submodule diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/config b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/config new file mode 100644 index 00000000000..7d002536a23 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_head + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/description b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/index b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/index new file mode 100644 index 00000000000..728fa292f5c Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/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/submod2/.gitted/modules/sm_changed_head/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD new file mode 100644 index 00000000000..cabdeb2b592 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/HEAD @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target +480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master new file mode 100644 index 00000000000..cabdeb2b592 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/heads/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target +480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..257ca21d175 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 new file mode 100644 index 00000000000..a2c371642c3 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 @@ -0,0 +1,3 @@ +xKj!E3vj#<<@Ouq .)ql osFa#v )g#{':Tl`b40 ;fr4 + +zU-/glm\'LjrhXG_+l ʚE`;=]J \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 new file mode 100644 index 00000000000..f8a236f3d34 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 new file mode 100644 index 00000000000..8155b3e87fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 @@ -0,0 +1,2 @@ +xMM; +1) ZPr3k lEnl!crz ,e +lEZxuPYx QC*fuLcfR3T0'үj~G^s1b2zVk]5<v'> \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master new file mode 100644 index 00000000000..ae079bd7926 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/heads/master @@ -0,0 +1 @@ +3d9386c507f6b093471a3e324085657a3c2b4247 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_head/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/config b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/config new file mode 100644 index 00000000000..0274ff7e337 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_index + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/description b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/index b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/index new file mode 100644 index 00000000000..6fad3b43eab Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/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/submod2/.gitted/modules/sm_changed_index/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD new file mode 100644 index 00000000000..80eb5410257 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master new file mode 100644 index 00000000000..80eb5410257 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..80eb5410257 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 new file mode 100644 index 00000000000..cb3f5a00261 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_index/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/config b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/config new file mode 100644 index 00000000000..7f2584476e7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_untracked_file + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/description b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/index b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/index new file mode 100644 index 00000000000..598e30a32c0 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/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/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD new file mode 100644 index 00000000000..d1beafbd6fb --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master new file mode 100644 index 00000000000..d1beafbd6fb --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..d1beafbd6fb --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/config b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/config new file mode 100644 index 00000000000..45fbb30cf0c --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_missing_commits + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/description b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/index b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/index new file mode 100644 index 00000000000..49035652457 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/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/submod2/.gitted/modules/sm_missing_commits/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD new file mode 100644 index 00000000000..ee08c9706af --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master new file mode 100644 index 00000000000..ee08c9706af --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..ee08c9706af --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs new file mode 100644 index 00000000000..66fbf5daf51 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +5e4963595a9774b90524d35a807169049de8ccad refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master new file mode 100644 index 00000000000..3913aca5dec --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/heads/master @@ -0,0 +1 @@ +5e4963595a9774b90524d35a807169049de8ccad diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_missing_commits/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/config b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/config new file mode 100644 index 00000000000..fc706c9dd9d --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_unchanged + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/description b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/hooks/applypatch-msg.sample b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/index b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/index new file mode 100644 index 00000000000..629c849ecfa Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/index differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/info/exclude b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/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/submod2/.gitted/modules/sm_unchanged/logs/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD new file mode 100644 index 00000000000..72653286ae8 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master new file mode 100644 index 00000000000..72653286ae8 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..72653286ae8 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/logs/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/packed-refs b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/packed-refs new file mode 100644 index 00000000000..5a4ebc47ccf --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD new file mode 100644 index 00000000000..6efe28fff83 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/modules/sm_unchanged/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/tests-clar/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e b/tests-clar/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e new file mode 100644 index 00000000000..f1ea5f4c8ec Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e differ diff --git a/tests-clar/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 b/tests-clar/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 new file mode 100644 index 00000000000..d3c8582e32e --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 @@ -0,0 +1 @@ +xM F]sfhcc;ހ@I(_O{s%@> ^!F'!諲l_q4E޶RݏS'n>>m^\w^$X_迦xE_.9} \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 b/tests-clar/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 new file mode 100644 index 00000000000..fce6a94b5c3 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 @@ -0,0 +1,4 @@ +x +0Eݶ_Q. +. W"!1 !3 >+.9=3W(n-:;j[" W{ޕQZW,2 iviyh T/={ !@b(bJcSPrŌ +{`|%imp콡=IÿW26B@)|)g \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 b/tests-clar/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 new file mode 100644 index 00000000000..2965becf606 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad b/tests-clar/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad new file mode 100644 index 00000000000..08faf0fa8e6 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad differ diff --git a/tests-clar/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 b/tests-clar/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 new file mode 100644 index 00000000000..ee7848ae6ef Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 b/tests-clar/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 new file mode 100644 index 00000000000..ca9203a6e3d Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b b/tests-clar/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b new file mode 100644 index 00000000000..9f88f6bdf72 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/59/01da4f1c67756eeadc5121d206bec2431f253b @@ -0,0 +1,2 @@ +x 1ENӀ2yg@D,A$;YE6m{΁e(/2d#jȚL 2Yd:fʞ =V^DhR $^a+tn {n8xs +Ծ=<@jCŹک[>6#=-g?,F \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d b/tests-clar/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d new file mode 100644 index 00000000000..30bee40e94f Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d differ diff --git a/tests-clar/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 b/tests-clar/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 new file mode 100644 index 00000000000..79018042d90 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/74/84482eb8db738cafa696993664607500a3f2b9 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 b/tests-clar/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 new file mode 100644 index 00000000000..cde89e5bbe2 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 b/tests-clar/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 new file mode 100644 index 00000000000..41af98aa9f2 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 b/tests-clar/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 new file mode 100644 index 00000000000..160f1caf4ae --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 @@ -0,0 +1,2 @@ +x +0Eݶ_Bqg yi IYUp!΁s|R7=)XCAG:25:<-uU_IY\Ϥ%AF f{G qTPsu(Z{RA #̉0mŲ.8b?{vʌ \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 b/tests-clar/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 new file mode 100644 index 00000000000..1ee52218d9f Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 b/tests-clar/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 new file mode 100644 index 00000000000..2239e14a831 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/a9/104bf89e911387244ef499413960ba472066d9 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb b/tests-clar/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb new file mode 100644 index 00000000000..a03ea66e4ae Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb differ diff --git a/tests-clar/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 b/tests-clar/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 new file mode 100644 index 00000000000..292303eb937 Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 differ diff --git a/tests-clar/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 b/tests-clar/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 new file mode 100644 index 00000000000..b92c7eebdc4 --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 @@ -0,0 +1,2 @@ +x +!nק} ".zuRCx}Αہt .׫6,is&%9S#IW=aߐf2ABYsНa{c^K3gwM͠Fߥ4s'NI \ No newline at end of file diff --git a/tests-clar/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c b/tests-clar/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c new file mode 100644 index 00000000000..3c7750b12ad Binary files /dev/null and b/tests-clar/resources/submod2/.gitted/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c differ diff --git a/tests-clar/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 b/tests-clar/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 new file mode 100644 index 00000000000..219620b251d --- /dev/null +++ b/tests-clar/resources/submod2/.gitted/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 @@ -0,0 +1,2 @@ +xe +0aSbOz12\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/post-update.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/post-update.sample new file mode 100755 index 00000000000..ec17ec1939b --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-applypatch.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-applypatch.sample new file mode 100755 index 00000000000..b1f187c2e9a --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-commit.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-commit.sample new file mode 100755 index 00000000000..18c48297652 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-commit.sample @@ -0,0 +1,50 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ascii filenames set this variable to true. +allownonascii=$(git config hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ascii filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + echo "Error: Attempt to add a non-ascii file name." + echo + echo "This can cause problems if you want to work" + echo "with people on other platforms." + echo + echo "To be portable it is advisable to rename the file ..." + echo + echo "If you know what you are doing you can disable this" + echo "check using:" + echo + echo " git config hooks.allownonascii true" + echo + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-rebase.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-rebase.sample new file mode 100755 index 00000000000..9773ed4cb29 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/prepare-commit-msg.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/prepare-commit-msg.sample new file mode 100755 index 00000000000..f093a02ec49 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/hooks/update.sample b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/update.sample new file mode 100755 index 00000000000..71ab04edc09 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to blocks unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/index b/tests-clar/resources/submod2/not_submodule/.gitted/index new file mode 100644 index 00000000000..f3fafa536b3 Binary files /dev/null and b/tests-clar/resources/submod2/not_submodule/.gitted/index differ diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/info/exclude b/tests-clar/resources/submod2/not_submodule/.gitted/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.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/submod2/not_submodule/.gitted/logs/HEAD b/tests-clar/resources/submod2/not_submodule/.gitted/logs/HEAD new file mode 100644 index 00000000000..1749e7dff82 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/logs/refs/heads/master b/tests-clar/resources/submod2/not_submodule/.gitted/logs/refs/heads/master new file mode 100644 index 00000000000..1749e7dff82 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 b/tests-clar/resources/submod2/not_submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 new file mode 100644 index 00000000000..8892531a749 Binary files /dev/null and b/tests-clar/resources/submod2/not_submodule/.gitted/objects/68/e92c611b80ee1ed8f38314ff9577f0d15b2444 differ diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 b/tests-clar/resources/submod2/not_submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 new file mode 100644 index 00000000000..c4e1a77d7e5 Binary files /dev/null and b/tests-clar/resources/submod2/not_submodule/.gitted/objects/71/ff9927d7c8a5639e062c38a7d35c433c424627 differ diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e b/tests-clar/resources/submod2/not_submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e new file mode 100644 index 00000000000..e9f1942a975 Binary files /dev/null and b/tests-clar/resources/submod2/not_submodule/.gitted/objects/f0/1d56b18efd353ef2bb93a4585d590a0847195e differ diff --git a/tests-clar/resources/submod2/not_submodule/.gitted/refs/heads/master b/tests-clar/resources/submod2/not_submodule/.gitted/refs/heads/master new file mode 100644 index 00000000000..0bd8514bd06 --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/.gitted/refs/heads/master @@ -0,0 +1 @@ +68e92c611b80ee1ed8f38314ff9577f0d15b2444 diff --git a/tests-clar/resources/submod2/not_submodule/README.txt b/tests-clar/resources/submod2/not_submodule/README.txt new file mode 100644 index 00000000000..71ff9927d7c --- /dev/null +++ b/tests-clar/resources/submod2/not_submodule/README.txt @@ -0,0 +1 @@ +This is a git repo but not a submodule diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/.gitted b/tests-clar/resources/submod2/sm_added_and_uncommited/.gitted new file mode 100644 index 00000000000..2b2a4cf9043 --- /dev/null +++ b/tests-clar/resources/submod2/sm_added_and_uncommited/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_added_and_uncommited diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/README.txt b/tests-clar/resources/submod2/sm_added_and_uncommited/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_added_and_uncommited/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_added_and_uncommited/file_to_modify b/tests-clar/resources/submod2/sm_added_and_uncommited/file_to_modify new file mode 100644 index 00000000000..789efbdadaa --- /dev/null +++ b/tests-clar/resources/submod2/sm_added_and_uncommited/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. diff --git a/tests-clar/resources/submod2/sm_changed_file/.gitted b/tests-clar/resources/submod2/sm_changed_file/.gitted new file mode 100644 index 00000000000..dc98b16740b --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_file/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_changed_file diff --git a/tests-clar/resources/submod2/sm_changed_file/README.txt b/tests-clar/resources/submod2/sm_changed_file/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_file/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_changed_file/file_to_modify b/tests-clar/resources/submod2/sm_changed_file/file_to_modify new file mode 100644 index 00000000000..e5ba6716859 --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_file/file_to_modify @@ -0,0 +1,4 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. +In this case, the file is changed in the workdir diff --git a/tests-clar/resources/submod2/sm_changed_head/.gitted b/tests-clar/resources/submod2/sm_changed_head/.gitted new file mode 100644 index 00000000000..d5419b62dc5 --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_head/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_changed_head diff --git a/tests-clar/resources/submod2/sm_changed_head/README.txt b/tests-clar/resources/submod2/sm_changed_head/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_head/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_changed_head/file_to_modify b/tests-clar/resources/submod2/sm_changed_head/file_to_modify new file mode 100644 index 00000000000..8eb1e637ed9 --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_head/file_to_modify @@ -0,0 +1,4 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. +This one has been changed and the change has been committed to HEAD. diff --git a/tests-clar/resources/submod2/sm_changed_index/.gitted b/tests-clar/resources/submod2/sm_changed_index/.gitted new file mode 100644 index 00000000000..2c7a5b2713d --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_index/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_changed_index diff --git a/tests-clar/resources/submod2/sm_changed_index/README.txt b/tests-clar/resources/submod2/sm_changed_index/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_index/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_changed_index/file_to_modify b/tests-clar/resources/submod2/sm_changed_index/file_to_modify new file mode 100644 index 00000000000..a02d3177068 --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_index/file_to_modify @@ -0,0 +1,4 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. +Here the file is changed in the index and the workdir diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/.gitted b/tests-clar/resources/submod2/sm_changed_untracked_file/.gitted new file mode 100644 index 00000000000..9a1070647dc --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_untracked_file/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_changed_untracked_file diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/README.txt b/tests-clar/resources/submod2/sm_changed_untracked_file/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_untracked_file/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/file_to_modify b/tests-clar/resources/submod2/sm_changed_untracked_file/file_to_modify new file mode 100644 index 00000000000..789efbdadaa --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_untracked_file/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. diff --git a/tests-clar/resources/submod2/sm_changed_untracked_file/i_am_untracked b/tests-clar/resources/submod2/sm_changed_untracked_file/i_am_untracked new file mode 100644 index 00000000000..d2bae6167cb --- /dev/null +++ b/tests-clar/resources/submod2/sm_changed_untracked_file/i_am_untracked @@ -0,0 +1 @@ +This file is untracked, but in a submodule diff --git a/tests-clar/resources/submod2/sm_missing_commits/.gitted b/tests-clar/resources/submod2/sm_missing_commits/.gitted new file mode 100644 index 00000000000..70193be84c7 --- /dev/null +++ b/tests-clar/resources/submod2/sm_missing_commits/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_missing_commits diff --git a/tests-clar/resources/submod2/sm_missing_commits/README.txt b/tests-clar/resources/submod2/sm_missing_commits/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_missing_commits/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_missing_commits/file_to_modify b/tests-clar/resources/submod2/sm_missing_commits/file_to_modify new file mode 100644 index 00000000000..8834b635dd4 --- /dev/null +++ b/tests-clar/resources/submod2/sm_missing_commits/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. + diff --git a/tests-clar/resources/submod2/sm_unchanged/.gitted b/tests-clar/resources/submod2/sm_unchanged/.gitted new file mode 100644 index 00000000000..51a679c80a1 --- /dev/null +++ b/tests-clar/resources/submod2/sm_unchanged/.gitted @@ -0,0 +1 @@ +gitdir: ../.git/modules/sm_unchanged diff --git a/tests-clar/resources/submod2/sm_unchanged/README.txt b/tests-clar/resources/submod2/sm_unchanged/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2/sm_unchanged/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2/sm_unchanged/file_to_modify b/tests-clar/resources/submod2/sm_unchanged/file_to_modify new file mode 100644 index 00000000000..789efbdadaa --- /dev/null +++ b/tests-clar/resources/submod2/sm_unchanged/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. diff --git a/tests-clar/resources/submod2_target/.gitted/HEAD b/tests-clar/resources/submod2_target/.gitted/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/submod2_target/.gitted/config b/tests-clar/resources/submod2_target/.gitted/config new file mode 100644 index 00000000000..af107929f2d --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/submod2_target/.gitted/description b/tests-clar/resources/submod2_target/.gitted/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/submod2_target/.gitted/hooks/applypatch-msg.sample b/tests-clar/resources/submod2_target/.gitted/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/submod2_target/.gitted/index b/tests-clar/resources/submod2_target/.gitted/index new file mode 100644 index 00000000000..eb3ff8c101b Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/index differ diff --git a/tests-clar/resources/submod2_target/.gitted/info/exclude b/tests-clar/resources/submod2_target/.gitted/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/submod2_target/.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/submod2_target/.gitted/logs/HEAD b/tests-clar/resources/submod2_target/.gitted/logs/HEAD new file mode 100644 index 00000000000..0ecd1113f4f --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/logs/HEAD @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit +6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file +41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file +5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/tests-clar/resources/submod2_target/.gitted/logs/refs/heads/master b/tests-clar/resources/submod2_target/.gitted/logs/refs/heads/master new file mode 100644 index 00000000000..0ecd1113f4f --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/logs/refs/heads/master @@ -0,0 +1,4 @@ +0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit +6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file +41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file +5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/tests-clar/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests-clar/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 00000000000..f4b7094c52b Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests-clar/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 00000000000..56c845e49de Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests-clar/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 00000000000..bd179b5f540 Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests-clar/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 00000000000..ccf49bd15ca Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests-clar/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 00000000000..53029069ade Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests-clar/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 00000000000..38c791eba1e Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests-clar/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 00000000000..a26d2999399 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/tests-clar/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests-clar/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 00000000000..83d1ba4813a Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests-clar/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 00000000000..6d27af8a891 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/tests-clar/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests-clar/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 00000000000..17458840b82 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/tests-clar/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests-clar/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 00000000000..83cc29fb159 Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests-clar/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests-clar/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 00000000000..55bda40ef27 Binary files /dev/null and b/tests-clar/resources/submod2_target/.gitted/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests-clar/resources/submod2_target/.gitted/refs/heads/master b/tests-clar/resources/submod2_target/.gitted/refs/heads/master new file mode 100644 index 00000000000..e12c44d7ae9 --- /dev/null +++ b/tests-clar/resources/submod2_target/.gitted/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/tests-clar/resources/submod2_target/README.txt b/tests-clar/resources/submod2_target/README.txt new file mode 100644 index 00000000000..780d7397f5e --- /dev/null +++ b/tests-clar/resources/submod2_target/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/tests-clar/resources/submod2_target/file_to_modify b/tests-clar/resources/submod2_target/file_to_modify new file mode 100644 index 00000000000..789efbdadaa --- /dev/null +++ b/tests-clar/resources/submod2_target/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. diff --git a/tests-clar/resources/template/branches/.gitignore b/tests-clar/resources/template/branches/.gitignore new file mode 100644 index 00000000000..16868cedb0b --- /dev/null +++ b/tests-clar/resources/template/branches/.gitignore @@ -0,0 +1,2 @@ +# This file should not be copied, nor should the +# containing directory, since it is effectively "empty" diff --git a/tests-clar/resources/template/description b/tests-clar/resources/template/description new file mode 100644 index 00000000000..498b267a8c7 --- /dev/null +++ b/tests-clar/resources/template/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/tests-clar/resources/template/hooks/applypatch-msg.sample b/tests-clar/resources/template/hooks/applypatch-msg.sample new file mode 100755 index 00000000000..8b2a2fe84fe --- /dev/null +++ b/tests-clar/resources/template/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +test -x "$GIT_DIR/hooks/commit-msg" && + exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"} +: diff --git a/tests-clar/resources/template/hooks/commit-msg.sample b/tests-clar/resources/template/hooks/commit-msg.sample new file mode 100755 index 00000000000..b58d1184a9d --- /dev/null +++ b/tests-clar/resources/template/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/tests-clar/resources/template/hooks/post-commit.sample b/tests-clar/resources/template/hooks/post-commit.sample new file mode 100755 index 00000000000..22668216a3c --- /dev/null +++ b/tests-clar/resources/template/hooks/post-commit.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script that is called after a successful +# commit is made. +# +# To enable this hook, rename this file to "post-commit". + +: Nothing diff --git a/tests-clar/resources/template/hooks/post-receive.sample b/tests-clar/resources/template/hooks/post-receive.sample new file mode 100755 index 00000000000..7a83e17ab5f --- /dev/null +++ b/tests-clar/resources/template/hooks/post-receive.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script for the "post-receive" event. +# +# The "post-receive" script is run after receive-pack has accepted a pack +# and the repository has been updated. It is passed arguments in through +# stdin in the form +# +# For example: +# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master +# +# see contrib/hooks/ for a sample, or uncomment the next line and +# rename the file to "post-receive". + +#. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/tests-clar/resources/template/hooks/post-update.sample b/tests-clar/resources/template/hooks/post-update.sample new file mode 100755 index 00000000000..ec17ec1939b --- /dev/null +++ b/tests-clar/resources/template/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/tests-clar/resources/template/hooks/pre-applypatch.sample b/tests-clar/resources/template/hooks/pre-applypatch.sample new file mode 100755 index 00000000000..b1f187c2e9a --- /dev/null +++ b/tests-clar/resources/template/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +test -x "$GIT_DIR/hooks/pre-commit" && + exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} +: diff --git a/tests-clar/resources/template/hooks/pre-commit.sample b/tests-clar/resources/template/hooks/pre-commit.sample new file mode 100755 index 00000000000..b187c4bb1f2 --- /dev/null +++ b/tests-clar/resources/template/hooks/pre-commit.sample @@ -0,0 +1,46 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ascii filenames set this variable to true. +allownonascii=$(git config hooks.allownonascii) + +# Cross platform projects tend to avoid non-ascii filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test "$(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0')" +then + echo "Error: Attempt to add a non-ascii file name." + echo + echo "This can cause problems if you want to work" + echo "with people on other platforms." + echo + echo "To be portable it is advisable to rename the file ..." + echo + echo "If you know what you are doing you can disable this" + echo "check using:" + echo + echo " git config hooks.allownonascii true" + echo + exit 1 +fi + +exec git diff-index --check --cached $against -- diff --git a/tests-clar/resources/template/hooks/pre-rebase.sample b/tests-clar/resources/template/hooks/pre-rebase.sample new file mode 100755 index 00000000000..9773ed4cb29 --- /dev/null +++ b/tests-clar/resources/template/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/tests-clar/resources/template/hooks/prepare-commit-msg.sample b/tests-clar/resources/template/hooks/prepare-commit-msg.sample new file mode 100755 index 00000000000..f093a02ec49 --- /dev/null +++ b/tests-clar/resources/template/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/tests-clar/resources/template/hooks/update.sample b/tests-clar/resources/template/hooks/update.sample new file mode 100755 index 00000000000..71ab04edc09 --- /dev/null +++ b/tests-clar/resources/template/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to blocks unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/tests-clar/resources/template/info/exclude b/tests-clar/resources/template/info/exclude new file mode 100644 index 00000000000..a5196d1be8f --- /dev/null +++ b/tests-clar/resources/template/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/testrepo.git/config b/tests-clar/resources/testrepo.git/config index b4fdac6c2f0..54ff6109bcc 100644 --- a/tests-clar/resources/testrepo.git/config +++ b/tests-clar/resources/testrepo.git/config @@ -6,7 +6,20 @@ [remote "test"] url = git://github.com/libgit2/libgit2 fetch = +refs/heads/*:refs/remotes/test/* +[remote "joshaber"] + url = git://github.com/libgit2/libgit2 + +[remote "test_with_pushurl"] + url = git://github.com/libgit2/fetchlibgit2 + pushurl = git://github.com/libgit2/pushlibgit2 + fetch = +refs/heads/*:refs/remotes/test_with_pushurl/* [branch "master"] remote = test merge = refs/heads/master +[branch "track-local"] + remote = . + merge = refs/heads/master +[branch "cannot-fetch"] + remote = joshaber + merge = refs/heads/cannot-fetch diff --git a/tests-clar/resources/testrepo.git/logs/HEAD b/tests-clar/resources/testrepo.git/logs/HEAD index b16c9b313a0..9413b72cc6d 100644 --- a/tests-clar/resources/testrepo.git/logs/HEAD +++ b/tests-clar/resources/testrepo.git/logs/HEAD @@ -1,5 +1,7 @@ 0000000000000000000000000000000000000000 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub 1335806563 -0700 clone: from /Users/ben/src/libgit2/tests/resources/testrepo.git be3563ae3f795b2b4353bcce3a527ad0a4f7f644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806603 -0900 commit: +a65fedf39aefe402d3bb6e24df4d4f5fe4547750 5b5b025afb0b4c913b4c338a42934a3863bf3644 Ben Straub 1335806604 -0900 checkout: moving from master to 5b5b025 +5b5b025afb0b4c913b4c338a42934a3863bf3644 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806605 -0900 checkout: moving from 5b5b025 to master a65fedf39aefe402d3bb6e24df4d4f5fe4547750 c47800c7266a2be04c571c04d5a6614691ea99bd Ben Straub 1335806608 -0900 checkout: moving from master to br2 c47800c7266a2be04c571c04d5a6614691ea99bd a4a7dce85cf63874e984719f4fdd239f5145052f Ben Straub 1335806617 -0900 commit: checking in a4a7dce85cf63874e984719f4fdd239f5145052f a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806621 -0900 checkout: moving from br2 to master diff --git a/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master b/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master new file mode 100644 index 00000000000..8d49ba3e031 --- /dev/null +++ b/tests-clar/resources/testrepo.git/logs/refs/remotes/test/master @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 a65fedf39aefe402d3bb6e24df4d4f5fe4547750 Ben Straub 1335806565 -0800 update by push +a65fedf39aefe402d3bb6e24df4d4f5fe4547750 be3563ae3f795b2b4353bcce3a527ad0a4f7f644 Ben Straub 1335806688 -0800 update by push diff --git a/tests-clar/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 b/tests-clar/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 new file mode 100644 index 00000000000..6048d4bad33 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/1b/8cbad43e867676df601306689fe7c3def5e689 differ diff --git a/tests-clar/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 b/tests-clar/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 new file mode 100644 index 00000000000..cb1ed5712c5 Binary files /dev/null and b/tests-clar/resources/testrepo.git/objects/25/8f0e2a959a364e40ed6603d5d44fbb24765b10 differ diff --git a/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch b/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch new file mode 100644 index 00000000000..aab87e5e723 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/cannot-fetch @@ -0,0 +1 @@ +a4a7dce85cf63874e984719f4fdd239f5145052f diff --git a/tests-clar/resources/testrepo.git/refs/heads/chomped b/tests-clar/resources/testrepo.git/refs/heads/chomped new file mode 100644 index 00000000000..0166a7f9227 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/chomped @@ -0,0 +1 @@ +e90810b8df3e80c413d903f631643c716887138d \ No newline at end of file diff --git a/tests-clar/resources/testrepo.git/refs/heads/haacked b/tests-clar/resources/testrepo.git/refs/heads/haacked new file mode 100644 index 00000000000..17f59122207 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/haacked @@ -0,0 +1 @@ +258f0e2a959a364e40ed6603d5d44fbb24765b10 diff --git a/tests-clar/resources/testrepo.git/refs/heads/track-local b/tests-clar/resources/testrepo.git/refs/heads/track-local new file mode 100644 index 00000000000..f37febb2cf2 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/track-local @@ -0,0 +1 @@ +9fd738e8f7967c078dceed8190330fc8648ee56a diff --git a/tests-clar/resources/testrepo.git/refs/heads/trailing b/tests-clar/resources/testrepo.git/refs/heads/trailing new file mode 100644 index 00000000000..2a4a6e62ff8 --- /dev/null +++ b/tests-clar/resources/testrepo.git/refs/heads/trailing @@ -0,0 +1 @@ +e90810b8df3e80c413d903f631643c716887138d diff --git a/tests-clar/resources/testrepo/.gitted/config b/tests-clar/resources/testrepo/.gitted/config index 1a5aacdfaee..d0114012f98 100644 --- a/tests-clar/resources/testrepo/.gitted/config +++ b/tests-clar/resources/testrepo/.gitted/config @@ -1,7 +1,7 @@ [core] repositoryformatversion = 0 filemode = true - bare = true + bare = false logallrefupdates = true [remote "test"] url = git://github.com/libgit2/libgit2 diff --git a/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff b/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff new file mode 100644 index 00000000000..c60c78fb5f2 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/objects/09/9fabac3a9ea935598528c27f866e34089c2eff @@ -0,0 +1 @@ +xQ P9^@B!1F'J?#7KJhMVE,.3uVsH-;U,MPIɉ&Ĕ׍סKO.2µո$8Nݗr!lCTklUgf0sÓG( \ No newline at end of file diff --git a/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e b/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e new file mode 100644 index 00000000000..b7d944fa117 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/14/4344043ba4d4a405da03de3844aa829ae8be0e differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 b/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 new file mode 100644 index 00000000000..d37b93e4fe9 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/16/8e4ebd1c667499548ae12403b19b22a5c5e925 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 b/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 new file mode 100644 index 00000000000..a83ed97630a Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/45/dd856fdd4d89b884c340ba0e047752d9b085d6 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 b/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 new file mode 100644 index 00000000000..e9150214bfe Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/4e/0883eeeeebc1fb1735161cea82f7cb5fab7e63 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc b/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc new file mode 100644 index 00000000000..b669961d8f9 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/62/eb56dabb4b9929bc15dd9263c2c733b13d2dcc differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 b/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 new file mode 100644 index 00000000000..9ff5eb2b5dd Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/66/3adb09143767984f7be83a91effa47e128c735 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 b/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 new file mode 100644 index 00000000000..3042f57909c Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/87/380ae84009e9c503506c2f6143a4fc6c60bf80 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e b/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e new file mode 100644 index 00000000000..0401ab48976 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/c0/528fd6cc988c0a40ce0be11bc192fc8dc5346e differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 b/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 new file mode 100644 index 00000000000..7620c514fb2 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/cf/80f8de9f1185bf3a05f993f6121880dd0cfbc9 differ diff --git a/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 b/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 new file mode 100644 index 00000000000..00940f0f286 Binary files /dev/null and b/tests-clar/resources/testrepo/.gitted/objects/d5/2a8fe84ceedf260afe4f0287bbfca04a117e83 differ diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/dir b/tests-clar/resources/testrepo/.gitted/refs/heads/dir new file mode 100644 index 00000000000..4567d37fa68 --- /dev/null +++ b/tests-clar/resources/testrepo/.gitted/refs/heads/dir @@ -0,0 +1 @@ +144344043ba4d4a405da03de3844aa829ae8be0e diff --git a/tests-clar/resources/testrepo/.gitted/refs/heads/master b/tests-clar/resources/testrepo/.gitted/refs/heads/master index 3d8f0a402b4..f31fe781bda 100644 --- a/tests-clar/resources/testrepo/.gitted/refs/heads/master +++ b/tests-clar/resources/testrepo/.gitted/refs/heads/master @@ -1 +1 @@ -a65fedf39aefe402d3bb6e24df4d4f5fe4547750 +099fabac3a9ea935598528c27f866e34089c2eff diff --git a/tests-clar/revwalk/basic.c b/tests-clar/revwalk/basic.c index a5a9b2edad4..126ca7d9f84 100644 --- a/tests-clar/revwalk/basic.c +++ b/tests-clar/revwalk/basic.c @@ -129,8 +129,8 @@ void test_revwalk_basic__glob_heads(void) i++; } - /* git log --branches --oneline | wc -l => 13 */ - cl_assert(i == 13); + /* git log --branches --oneline | wc -l => 14 */ + cl_assert(i == 14); } void test_revwalk_basic__push_head(void) @@ -179,3 +179,11 @@ void test_revwalk_basic__push_head_hide_ref_nobase(void) /* git log HEAD --oneline --not refs/heads/packed | wc -l => 7 */ cl_assert(i == 7); } + +void test_revwalk_basic__disallow_non_commit(void) +{ + git_oid oid; + + cl_git_pass(git_oid_fromstr(&oid, "521d87c1ec3aef9824daf6d96cc0ae3710766d91")); + cl_git_fail(git_revwalk_push(_walk, &oid)); +} diff --git a/tests-clar/revwalk/signatureparsing.c b/tests-clar/revwalk/signatureparsing.c new file mode 100644 index 00000000000..94de1a34368 --- /dev/null +++ b/tests-clar/revwalk/signatureparsing.c @@ -0,0 +1,44 @@ +#include "clar_libgit2.h" + +static git_repository *_repo; +static git_revwalk *_walk; + +void test_revwalk_signatureparsing__initialize(void) +{ + cl_git_pass(git_repository_open(&_repo, cl_fixture("testrepo.git"))); + cl_git_pass(git_revwalk_new(&_walk, _repo)); +} + +void test_revwalk_signatureparsing__cleanup(void) +{ + git_revwalk_free(_walk); + git_repository_free(_repo); +} + +void test_revwalk_signatureparsing__do_not_choke_when_name_contains_angle_brackets(void) +{ + git_reference *ref; + git_oid commit_oid; + git_commit *commit; + const git_signature *signature; + + /* + * The branch below points at a commit with angle brackets in the committer/author name + * committer 1323847743 +0100 + */ + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/haacked")); + + git_revwalk_push(_walk, git_reference_oid(ref)); + cl_git_pass(git_revwalk_next(&commit_oid, _walk)); + + cl_git_pass(git_commit_lookup(&commit, _repo, git_reference_oid(ref))); + + signature = git_commit_committer(commit); + cl_assert_equal_s("Yu V. Bin Haacked", signature->email); + cl_assert_equal_s("", signature->name); + cl_assert_equal_i(1323847743, (int)signature->when.time); + cl_assert_equal_i(60, signature->when.offset); + + git_commit_free(commit); + git_reference_free(ref); +} diff --git a/tests-clar/status/ignore.c b/tests-clar/status/ignore.c index 0384306c12d..9092d51552a 100644 --- a/tests-clar/status/ignore.c +++ b/tests-clar/status/ignore.c @@ -139,9 +139,78 @@ void test_status_ignore__ignore_pattern_contains_space(void) g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_git_rewritefile("empty_standard_repo/.gitignore", "foo bar.txt\n"); + cl_git_mkfile( + "empty_standard_repo/foo bar.txt", "I'm going to be ignored!"); + + cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt")); + cl_assert(flags == GIT_STATUS_IGNORED); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode)); cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!"); cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt")); cl_assert(flags == GIT_STATUS_WT_NEW); } + +void test_status_ignore__adding_internal_ignores(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(!ignored); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.nomatch\n")); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(!ignored); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.txt\n")); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(!ignored); + + cl_git_pass(git_ignore_add_rule(g_repo, "*.bar\n")); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(ignored); + + cl_git_pass(git_ignore_clear_internal_rules(g_repo)); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(!ignored); + + cl_git_pass(git_ignore_add_rule( + g_repo, "multiple\n*.rules\n# comment line\n*.bar\n")); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.txt")); + cl_assert(!ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(ignored); +} + +void test_status_ignore__add_internal_as_first_thing(void) +{ + int ignored; + const char *add_me = "\n#################\n## Eclipse\n#################\n\n*.pydevproject\n.project\n.metadata\nbin/\ntmp/\n*.tmp\n\n"; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_pass(git_ignore_add_rule(g_repo, add_me)); + + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "one.tmp")); + cl_assert(ignored); + cl_git_pass(git_status_should_ignore(&ignored, g_repo, "two.bar")); + cl_assert(!ignored); +} diff --git a/tests-clar/status/status_data.h b/tests-clar/status/status_data.h index 043b8300984..85a7cd6b5ae 100644 --- a/tests-clar/status/status_data.h +++ b/tests-clar/status/status_data.h @@ -44,7 +44,7 @@ static const unsigned int entry_statuses0[] = { GIT_STATUS_WT_NEW, }; -static const size_t entry_count0 = 16; +static const int entry_count0 = 16; /* entries for a copy of tests/resources/status with all content * deleted from the working directory @@ -86,7 +86,7 @@ static const unsigned int entry_statuses2[] = { GIT_STATUS_WT_DELETED, }; -static const size_t entry_count2 = 15; +static const int entry_count2 = 15; /* entries for a copy of tests/resources/status with some mods */ @@ -140,7 +140,7 @@ static const unsigned int entry_statuses3[] = { GIT_STATUS_WT_NEW, }; -static const size_t entry_count3 = 22; +static const int entry_count3 = 22; /* entries for a copy of tests/resources/status with some mods @@ -199,4 +199,4 @@ static const unsigned int entry_statuses4[] = { GIT_STATUS_WT_NEW, }; -static const size_t entry_count4 = 23; +static const int entry_count4 = 23; diff --git a/tests-clar/status/status_helpers.h b/tests-clar/status/status_helpers.h index cffca66a577..3f9c1f57d1b 100644 --- a/tests-clar/status/status_helpers.h +++ b/tests-clar/status/status_helpers.h @@ -2,12 +2,12 @@ #define INCLUDE_cl_status_helpers_h__ typedef struct { - size_t wrong_status_flags_count; - size_t wrong_sorted_path; - size_t entry_count; + int wrong_status_flags_count; + int wrong_sorted_path; + int entry_count; const unsigned int* expected_statuses; const char** expected_paths; - size_t expected_entry_count; + int expected_entry_count; } status_entry_counts; /* cb_status__normal takes payload of "status_entry_counts *" */ diff --git a/tests-clar/status/submodules.c b/tests-clar/status/submodules.c index 9423e849049..24dd660aba9 100644 --- a/tests-clar/status/submodules.c +++ b/tests-clar/status/submodules.c @@ -3,24 +3,17 @@ #include "path.h" #include "posix.h" #include "status_helpers.h" +#include "../submodule/submodule_helpers.h" static git_repository *g_repo = NULL; void test_status_submodules__initialize(void) { - git_buf modpath = GIT_BUF_INIT; - g_repo = cl_git_sandbox_init("submodules"); cl_fixture_sandbox("testrepo.git"); - cl_git_pass(git_buf_sets(&modpath, git_repository_workdir(g_repo))); - cl_assert(git_path_dirname_r(&modpath, modpath.ptr) >= 0); - cl_git_pass(git_buf_joinpath(&modpath, modpath.ptr, "testrepo.git\n")); - - p_rename("submodules/gitmodules", "submodules/.gitmodules"); - cl_git_append2file("submodules/.gitmodules", modpath.ptr); - git_buf_free(&modpath); + rewrite_gitmodules(git_repository_workdir(g_repo)); p_rename("submodules/testrepo/.gitted", "submodules/testrepo/.git"); } @@ -28,6 +21,7 @@ void test_status_submodules__initialize(void) void test_status_submodules__cleanup(void) { cl_git_sandbox_cleanup(); + cl_fixture_cleanup("testrepo.git"); } void test_status_submodules__api(void) @@ -40,8 +34,8 @@ void test_status_submodules__api(void) cl_git_pass(git_submodule_lookup(&sm, g_repo, "testrepo")); cl_assert(sm != NULL); - cl_assert_equal_s("testrepo", sm->name); - cl_assert_equal_s("testrepo", sm->path); + cl_assert_equal_s("testrepo", git_submodule_name(sm)); + cl_assert_equal_s("testrepo", git_submodule_path(sm)); } void test_status_submodules__0(void) @@ -56,7 +50,7 @@ void test_status_submodules__0(void) git_status_foreach(g_repo, cb_status__count, &counts) ); - cl_assert(counts == 6); + cl_assert_equal_i(6, counts); } static const char *expected_files[] = { @@ -101,12 +95,12 @@ void test_status_submodules__1(void) git_status_foreach(g_repo, cb_status__match, &index) ); - cl_assert(index == 6); + cl_assert_equal_i(6, index); } void test_status_submodules__single_file(void) { - unsigned int status; + unsigned int status = 0; cl_git_pass( git_status_file(&status, g_repo, "testrepo") ); - cl_assert(status == 0); + cl_assert(!status); } diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 3670b72a87e..05e396e1f3f 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -459,7 +459,7 @@ void test_status_worktree__status_file_without_index_or_workdir(void) cl_git_pass(p_mkdir("wd", 0777)); cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd")); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); cl_git_pass(git_index_open(&index, "empty-index")); cl_assert_equal_i(0, git_index_entrycount(index)); @@ -484,7 +484,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)); + cl_git_pass(git_index_read_tree(index, tree, NULL)); cl_git_pass(git_index_write(index)); git_tree_free(tree); @@ -500,7 +500,7 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) cl_git_pass(p_mkdir("wd", 0777)); cl_git_pass(git_repository_open(&repo, cl_fixture("testrepo.git"))); - cl_git_pass(git_repository_set_workdir(repo, "wd")); + cl_git_pass(git_repository_set_workdir(repo, "wd", false)); cl_git_pass(git_index_open(&index, "my-index")); fill_index_wth_head_entries(repo, index); @@ -517,6 +517,85 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void) cl_git_pass(p_unlink("my-index")); } +void test_status_worktree__bracket_in_filename(void) +{ + git_repository *repo; + git_index *index; + status_entry_single result; + unsigned int status_flags; + int error; + + #define FILE_WITH_BRACKET "LICENSE[1].md" + #define FILE_WITHOUT_BRACKET "LICENSE1.md" + + cl_git_pass(git_repository_init(&repo, "with_bracket", 0)); + cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n"); + + /* file is new to working directory */ + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(1, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_IGNORED); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_IGNORED); + + /* don't ignore the file */ + + cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n"); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + /* add the file to the index */ + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0)); + cl_git_pass(git_index_write(index)); + + memset(&result, 0, sizeof(result)); + cl_git_pass(git_status_foreach(repo, cb_status__single, &result)); + cl_assert_equal_i(2, result.count); + cl_assert(result.status == GIT_STATUS_INDEX_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + /* Create file without bracket */ + + cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n"); + + cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); + cl_assert(status_flags == GIT_STATUS_WT_NEW); + + cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + + error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET); + cl_git_fail(error); + cl_assert_equal_i(GIT_EAMBIGUOUS, error); + + git_index_free(index); + git_repository_free(repo); +} void test_status_worktree__space_in_filename(void) { @@ -604,7 +683,7 @@ static unsigned int filemode_statuses[] = { GIT_STATUS_WT_NEW }; -static const size_t filemode_count = 8; +static const int filemode_count = 8; void test_status_worktree__filemode_changes(void) { @@ -618,7 +697,7 @@ void test_status_worktree__filemode_changes(void) if (cl_is_chmod_supported()) cl_git_pass(git_config_set_bool(cfg, "core.filemode", true)); else { - unsigned int i; + int i; cl_git_pass(git_config_set_bool(cfg, "core.filemode", false)); /* won't trust filesystem mode diffs, so these will appear unchanged */ @@ -647,3 +726,118 @@ void test_status_worktree__filemode_changes(void) git_config_free(cfg); } + +static int cb_status__expected_path(const char *p, unsigned int s, void *payload) +{ + const char *expected_path = (const char *)payload; + + GIT_UNUSED(s); + + if (payload == NULL) + cl_fail("Unexpected path"); + + cl_assert_equal_s(expected_path, p); + + return 0; +} + +void test_status_worktree__disable_pathspec_match(void) +{ + git_repository *repo; + git_status_options opts; + char *file_with_bracket = "LICENSE[1].md", + *imaginary_file_with_bracket = "LICENSE[1-2].md"; + + cl_git_pass(git_repository_init(&repo, "pathspec", 0)); + cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n"); + cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n"); + + memset(&opts, 0, sizeof(opts)); + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + opts.pathspec.count = 1; + opts.pathspec.strings = &file_with_bracket; + + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, + file_with_bracket) + ); + + /* Test passing a pathspec matching files in the workdir. */ + /* Must not match because pathspecs are disabled. */ + opts.pathspec.strings = &imaginary_file_with_bracket; + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL) + ); + + git_repository_free(repo); +} + + +static int cb_status__interrupt(const char *p, unsigned int s, void *payload) +{ + volatile int *count = (int *)payload; + + GIT_UNUSED(p); + GIT_UNUSED(s); + + (*count)++; + + return (*count == 8); +} + +void test_status_worktree__interruptable_foreach(void) +{ + int count = 0; + git_repository *repo = cl_git_sandbox_init("status"); + + cl_assert_equal_i( + GIT_EUSER, git_status_foreach(repo, cb_status__interrupt, &count) + ); + + cl_assert_equal_i(8, count); +} + +void test_status_worktree__new_staged_file_must_handle_crlf(void) +{ + git_repository *repo; + git_index *index; + git_config *config; + unsigned int status; + + cl_git_pass(git_repository_init(&repo, "getting_started", 0)); + + // Ensure that repo has core.autocrlf=true + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + + 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_write(index)); + + cl_git_pass(git_status_file(&status, repo, "testfile.txt")); + cl_assert_equal_i(GIT_STATUS_INDEX_NEW, status); + + git_config_free(config); + git_index_free(index); + git_repository_free(repo); +} + +void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void) +{ + git_repository *repo = cl_git_sandbox_init("status"); + git_config *config; + unsigned int status; + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "core.autocrlf", true)); + git_config_free(config); + + cl_git_rewritefile("status/current_file", "current_file\r\n"); + + cl_git_pass(git_status_file(&status, repo, "current_file")); + + cl_assert_equal_i(GIT_STATUS_CURRENT, status); +} diff --git a/tests-clar/submodule/lookup.c b/tests-clar/submodule/lookup.c new file mode 100644 index 00000000000..94eb19b5e53 --- /dev/null +++ b/tests-clar/submodule/lookup.c @@ -0,0 +1,114 @@ +#include "clar_libgit2.h" +#include "submodule_helpers.h" +#include "posix.h" + +static git_repository *g_repo = NULL; + +void test_submodule_lookup__initialize(void) +{ + g_repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + /* must create submod2_target before rewrite so prettify will work */ + rewrite_gitmodules(git_repository_workdir(g_repo)); + p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git"); +} + +void test_submodule_lookup__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("submod2_target"); +} + +void test_submodule_lookup__simple_lookup(void) +{ + git_submodule *sm; + + /* lookup existing */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is not in HEAD */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert(sm); + + /* lookup pending change in .gitmodules that is neither in HEAD nor index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_gitmodules_only")); + cl_assert(sm); + + /* lookup git repo subdir that is not added as submodule */ + cl_assert(git_submodule_lookup(&sm, g_repo, "not_submodule") == GIT_EEXISTS); + + /* lookup existing directory that is not a submodule */ + cl_assert(git_submodule_lookup(&sm, g_repo, "just_a_dir") == GIT_ENOTFOUND); + + /* lookup existing file that is not a submodule */ + cl_assert(git_submodule_lookup(&sm, g_repo, "just_a_file") == GIT_ENOTFOUND); + + /* lookup non-existent item */ + cl_assert(git_submodule_lookup(&sm, g_repo, "no_such_file") == GIT_ENOTFOUND); +} + +void test_submodule_lookup__accessors(void) +{ + git_submodule *sm; + const char *oid = "480095882d281ed676fe5b863569520e54a7d5c0"; + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_assert(git_submodule_owner(sm) == g_repo); + cl_assert_equal_s("sm_unchanged", git_submodule_name(sm)); + cl_assert(git__suffixcmp(git_submodule_path(sm), "sm_unchanged") == 0); + cl_assert(git__suffixcmp(git_submodule_url(sm), "/submod2_target") == 0); + + cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_oid(sm), oid) == 0); + + cl_assert(git_submodule_ignore(sm) == GIT_SUBMODULE_IGNORE_NONE); + cl_assert(git_submodule_update(sm) == GIT_SUBMODULE_UPDATE_CHECKOUT); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_assert_equal_s("sm_changed_head", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_oid(sm), + "3d9386c507f6b093471a3e324085657a3c2b4247") == 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_assert_equal_s("sm_added_and_uncommited", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0); + cl_assert(git_submodule_head_oid(sm) == NULL); + cl_assert(git_oid_streq(git_submodule_wd_oid(sm), oid) == 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_assert_equal_s("sm_missing_commits", git_submodule_name(sm)); + + cl_assert(git_oid_streq(git_submodule_index_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_head_oid(sm), oid) == 0); + cl_assert(git_oid_streq(git_submodule_wd_oid(sm), + "5e4963595a9774b90524d35a807169049de8ccad") == 0); +} + +typedef struct { + int count; +} sm_lookup_data; + +static int sm_lookup_cb(git_submodule *sm, const char *name, void *payload) +{ + sm_lookup_data *data = payload; + data->count += 1; + cl_assert_equal_s(git_submodule_name(sm), name); + return 0; +} + +void test_submodule_lookup__foreach(void) +{ + sm_lookup_data data; + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} diff --git a/tests-clar/submodule/modify.c b/tests-clar/submodule/modify.c new file mode 100644 index 00000000000..0fd732cc3e2 --- /dev/null +++ b/tests-clar/submodule/modify.c @@ -0,0 +1,268 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" + +static git_repository *g_repo = NULL; + +#define SM_LIBGIT2_URL "https://github.com/libgit2/libgit2.git" +#define SM_LIBGIT2 "sm_libgit2" +#define SM_LIBGIT2B "sm_libgit2b" + +void test_submodule_modify__initialize(void) +{ + g_repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + /* must create submod2_target before rewrite so prettify will work */ + rewrite_gitmodules(git_repository_workdir(g_repo)); + p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git"); +} + +void test_submodule_modify__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("submod2_target"); +} + +void test_submodule_modify__add(void) +{ + git_submodule *sm; + git_config *cfg; + const char *s; + + /* re-add existing submodule */ + cl_assert( + git_submodule_add_setup(NULL, g_repo, "whatever", "sm_unchanged", 1) == + GIT_EEXISTS ); + + /* add a submodule using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2, 1) + ); + + cl_assert(git_path_isfile("submod2/" SM_LIBGIT2 "/.git")); + + cl_assert(git_path_isdir("submod2/.git/modules")); + cl_assert(git_path_isdir("submod2/.git/modules/" SM_LIBGIT2)); + cl_assert(git_path_isfile("submod2/.git/modules/" SM_LIBGIT2 "/HEAD")); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass( + git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2 ".url")); + cl_assert_equal_s(s, SM_LIBGIT2_URL); + git_config_free(cfg); + + /* add a submodule not using a gitlink */ + + cl_git_pass( + git_submodule_add_setup(&sm, g_repo, SM_LIBGIT2_URL, SM_LIBGIT2B, 0) + ); + + cl_assert(git_path_isdir("submod2/" SM_LIBGIT2B "/.git")); + cl_assert(git_path_isfile("submod2/" SM_LIBGIT2B "/.git/HEAD")); + cl_assert(!git_path_exists("submod2/.git/modules/" SM_LIBGIT2B)); + + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass( + git_config_get_string(&s, cfg, "submodule." SM_LIBGIT2B ".url")); + cl_assert_equal_s(s, SM_LIBGIT2_URL); + git_config_free(cfg); +} + +static int delete_one_config( + const char *var_name, const char *value, void *payload) +{ + git_config *cfg = payload; + GIT_UNUSED(value); + return git_config_delete(cfg, var_name); +} + +static int init_one_submodule( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(payload); + return git_submodule_init(sm, false); +} + +void test_submodule_modify__init(void) +{ + git_config *cfg; + const char *str; + + /* erase submodule data from .git/config */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass( + git_config_foreach_match(cfg, "submodule\\..*", delete_one_config, cfg)); + git_config_free(cfg); + + /* confirm no submodule data in config */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); + cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); + cl_git_fail(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); + git_config_free(cfg); + + /* call init and see that settings are copied */ + cl_git_pass(git_submodule_foreach(g_repo, init_one_submodule, NULL)); + + git_submodule_reload_all(g_repo); + + /* confirm submodule data in config */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_unchanged.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_changed_head.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule.sm_added_and_uncommited.url")); + cl_assert(git__suffixcmp(str, "/submod2_target") == 0); + git_config_free(cfg); +} + +static int sync_one_submodule( + git_submodule *sm, const char *name, void *payload) +{ + GIT_UNUSED(name); + GIT_UNUSED(payload); + return git_submodule_sync(sm); +} + +void test_submodule_modify__sync(void) +{ + git_submodule *sm1, *sm2, *sm3; + git_config *cfg; + const char *str; + +#define SM1 "sm_unchanged" +#define SM2 "sm_changed_head" +#define SM3 "sm_added_and_uncommited" + + /* look up some submodules */ + cl_git_pass(git_submodule_lookup(&sm1, g_repo, SM1)); + cl_git_pass(git_submodule_lookup(&sm2, g_repo, SM2)); + cl_git_pass(git_submodule_lookup(&sm3, g_repo, SM3)); + + /* At this point, the .git/config URLs for the submodules have + * not be rewritten with the absolute paths (although the + * .gitmodules have. Let's confirm that they DO NOT match + * yet, then we can do a sync to make them match... + */ + + /* check submodule info does not match before sync */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url")); + cl_assert(strcmp(git_submodule_url(sm1), str) != 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url")); + cl_assert(strcmp(git_submodule_url(sm2), str) != 0); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url")); + cl_assert(strcmp(git_submodule_url(sm3), str) != 0); + git_config_free(cfg); + + /* sync all the submodules */ + cl_git_pass(git_submodule_foreach(g_repo, sync_one_submodule, NULL)); + + /* check that submodule config is updated */ + cl_git_pass(git_repository_config(&cfg, g_repo)); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM1".url")); + cl_assert_equal_s(git_submodule_url(sm1), str); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM2".url")); + cl_assert_equal_s(git_submodule_url(sm2), str); + cl_git_pass(git_config_get_string(&str, cfg, "submodule."SM3".url")); + cl_assert_equal_s(git_submodule_url(sm3), str); + git_config_free(cfg); +} + +void test_submodule_modify__edit_and_save(void) +{ + git_submodule *sm1, *sm2; + char *old_url; + git_submodule_ignore_t old_ignore; + git_submodule_update_t old_update; + git_repository *r2; + int old_fetchrecurse; + + cl_git_pass(git_submodule_lookup(&sm1, g_repo, "sm_changed_head")); + + old_url = git__strdup(git_submodule_url(sm1)); + + /* modify properties of submodule */ + cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL)); + old_ignore = git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED); + old_update = git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE); + old_fetchrecurse = git_submodule_set_fetch_recurse_submodules(sm1, 1); + + cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); + + /* revert without saving (and confirm setters return old value) */ + cl_git_pass(git_submodule_set_url(sm1, old_url)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_IGNORE_UNTRACKED, + (int)git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_UPDATE_REBASE, + (int)git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT)); + cl_assert_equal_i( + 1, git_submodule_set_fetch_recurse_submodules(sm1, old_fetchrecurse)); + + /* check that revert was successful */ + cl_assert_equal_s(old_url, git_submodule_url(sm1)); + cl_assert_equal_i((int)old_ignore, (int)git_submodule_ignore(sm1)); + cl_assert_equal_i((int)old_update, (int)git_submodule_update(sm1)); + cl_assert_equal_i( + old_fetchrecurse, git_submodule_fetch_recurse_submodules(sm1)); + + /* modify properties of submodule (again) */ + cl_git_pass(git_submodule_set_url(sm1, SM_LIBGIT2_URL)); + git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_UNTRACKED); + git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_REBASE); + git_submodule_set_fetch_recurse_submodules(sm1, 1); + + /* call save */ + cl_git_pass(git_submodule_save(sm1)); + + /* attempt to "revert" values */ + git_submodule_set_ignore(sm1, GIT_SUBMODULE_IGNORE_DEFAULT); + git_submodule_set_update(sm1, GIT_SUBMODULE_UPDATE_DEFAULT); + + /* but ignore and update should NOT revert because the DEFAULT + * should now be the newly saved value... + */ + cl_assert_equal_i( + (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); + + /* call reload and check that the new values are loaded */ + cl_git_pass(git_submodule_reload(sm1)); + + cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm1)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm1)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm1)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm1)); + + /* open a second copy of the repo and compare submodule */ + cl_git_pass(git_repository_open(&r2, "submod2")); + cl_git_pass(git_submodule_lookup(&sm2, r2, "sm_changed_head")); + + cl_assert_equal_s(SM_LIBGIT2_URL, git_submodule_url(sm2)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_IGNORE_UNTRACKED, (int)git_submodule_ignore(sm2)); + cl_assert_equal_i( + (int)GIT_SUBMODULE_UPDATE_REBASE, (int)git_submodule_update(sm2)); + cl_assert_equal_i(1, git_submodule_fetch_recurse_submodules(sm2)); + + git_repository_free(r2); + git__free(old_url); +} diff --git a/tests-clar/submodule/status.c b/tests-clar/submodule/status.c new file mode 100644 index 00000000000..d3a39235a1c --- /dev/null +++ b/tests-clar/submodule/status.c @@ -0,0 +1,308 @@ +#include "clar_libgit2.h" +#include "posix.h" +#include "path.h" +#include "submodule_helpers.h" +#include "fileops.h" + +static git_repository *g_repo = NULL; + +void test_submodule_status__initialize(void) +{ + g_repo = cl_git_sandbox_init("submod2"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + /* must create submod2_target before rewrite so prettify will work */ + rewrite_gitmodules(git_repository_workdir(g_repo)); + p_rename("submod2/not_submodule/.gitted", "submod2/not_submodule/.git"); +} + +void test_submodule_status__cleanup(void) +{ + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("submod2_target"); +} + +void test_submodule_status__unchanged(void) +{ + unsigned int status, expected; + git_submodule *sm; + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + expected = GIT_SUBMODULE_STATUS_IN_HEAD | + GIT_SUBMODULE_STATUS_IN_INDEX | + GIT_SUBMODULE_STATUS_IN_CONFIG | + GIT_SUBMODULE_STATUS_IN_WD; + + cl_assert(status == expected); +} + +/* 4 values of GIT_SUBMODULE_IGNORE to check */ + +void test_submodule_status__ignore_none(void) +{ + unsigned int status; + git_submodule *sm; + 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), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNTRACKED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + /* remove sm_changed_head from index */ + { + git_index *index; + int pos; + + 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_write(index)); + + git_index_free(index); + } + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_DELETED) != 0); + + git_buf_free(&path); +} + +static int set_sm_ignore(git_submodule *sm, const char *name, void *payload) +{ + git_submodule_ignore_t ignore = *(git_submodule_ignore_t *)payload; + GIT_UNUSED(name); + git_submodule_set_ignore(sm, ignore); + return 0; +} + +void test_submodule_status__ignore_untracked(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + 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), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_INDEX_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + git_buf_free(&path); +} + +void test_submodule_status__ignore_dirty(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + 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), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_MODIFIED) != 0); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_ADDED) != 0); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert((status & GIT_SUBMODULE_STATUS_INDEX_MODIFIED) != 0); + + git_buf_free(&path); +} + +void test_submodule_status__ignore_all(void) +{ + unsigned int status; + git_submodule *sm; + git_buf path = GIT_BUF_INIT; + 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), GIT_DIRREMOVAL_FILES_AND_DIRS)); + + cl_git_pass(git_submodule_foreach(g_repo, set_sm_ignore, &ign)); + + cl_git_fail(git_submodule_lookup(&sm, g_repo, "not_submodule")); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_index")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_untracked_file")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_missing_commits")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_added_and_uncommited")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* removed sm_unchanged for deleted workdir */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* now mkdir sm_unchanged to test uninitialized */ + cl_git_pass(git_futils_mkdir(git_buf_cstr(&path), NULL, 0755, 0)); + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_unchanged")); + cl_git_pass(git_submodule_reload(sm)); + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + /* update sm_changed_head in index */ + cl_git_pass(git_submodule_lookup(&sm, g_repo, "sm_changed_head")); + cl_git_pass(git_submodule_add_to_index(sm, true)); + /* reload is not needed because add_to_index updates the submodule data */ + cl_git_pass(git_submodule_status(&status, sm)); + cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); + + git_buf_free(&path); +} diff --git a/tests-clar/submodule/submodule_helpers.c b/tests-clar/submodule/submodule_helpers.c new file mode 100644 index 00000000000..0c3e79f717e --- /dev/null +++ b/tests-clar/submodule/submodule_helpers.c @@ -0,0 +1,84 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "path.h" +#include "util.h" +#include "posix.h" +#include "submodule_helpers.h" + +/* rewrite gitmodules -> .gitmodules + * rewrite the empty or relative urls inside each module + * rename the .gitted directory inside any submodule to .git + */ +void rewrite_gitmodules(const char *workdir) +{ + git_buf in_f = GIT_BUF_INIT, out_f = GIT_BUF_INIT, path = GIT_BUF_INIT; + FILE *in, *out; + char line[256]; + + cl_git_pass(git_buf_joinpath(&in_f, workdir, "gitmodules")); + cl_git_pass(git_buf_joinpath(&out_f, workdir, ".gitmodules")); + + cl_assert((in = fopen(in_f.ptr, "r")) != NULL); + cl_assert((out = fopen(out_f.ptr, "w")) != NULL); + + while (fgets(line, sizeof(line), in) != NULL) { + char *scan = line; + + while (*scan == ' ' || *scan == '\t') scan++; + + /* rename .gitted -> .git in submodule directories */ + if (git__prefixcmp(scan, "path =") == 0) { + scan += strlen("path ="); + while (*scan == ' ') scan++; + + git_buf_joinpath(&path, workdir, scan); + git_buf_rtrim(&path); + git_buf_joinpath(&path, path.ptr, ".gitted"); + + if (!git_buf_oom(&path) && p_access(path.ptr, F_OK) == 0) { + git_buf_joinpath(&out_f, workdir, scan); + git_buf_rtrim(&out_f); + git_buf_joinpath(&out_f, out_f.ptr, ".git"); + + if (!git_buf_oom(&out_f)) + p_rename(path.ptr, out_f.ptr); + } + } + + /* copy non-"url =" lines verbatim */ + if (git__prefixcmp(scan, "url =") != 0) { + fputs(line, out); + continue; + } + + /* convert relative URLs in "url =" lines */ + scan += strlen("url ="); + while (*scan == ' ') scan++; + + if (*scan == '.') { + git_buf_joinpath(&path, workdir, scan); + git_buf_rtrim(&path); + } else if (!*scan || *scan == '\n') { + git_buf_joinpath(&path, workdir, "../testrepo.git"); + } else { + fputs(line, out); + continue; + } + + git_path_prettify(&path, path.ptr, NULL); + git_buf_putc(&path, '\n'); + cl_assert(!git_buf_oom(&path)); + + fwrite(line, scan - line, sizeof(char), out); + fputs(path.ptr, out); + } + + fclose(in); + fclose(out); + + cl_must_pass(p_unlink(in_f.ptr)); + + git_buf_free(&in_f); + git_buf_free(&out_f); + git_buf_free(&path); +} diff --git a/tests-clar/submodule/submodule_helpers.h b/tests-clar/submodule/submodule_helpers.h new file mode 100644 index 00000000000..6b76a832e9a --- /dev/null +++ b/tests-clar/submodule/submodule_helpers.h @@ -0,0 +1,2 @@ +extern void rewrite_gitmodules(const char *workdir); + diff --git a/tests-clar/valgrind-supp-mac.txt b/tests-clar/valgrind-supp-mac.txt new file mode 100644 index 00000000000..03e60dcd773 --- /dev/null +++ b/tests-clar/valgrind-supp-mac.txt @@ -0,0 +1,82 @@ +{ + libgit2-giterr-set-buffer + Memcheck:Leak + ... + fun:git__realloc + fun:git_buf_try_grow + fun:git_buf_grow + fun:git_buf_vprintf + fun:giterr_set +} +{ + mac-setenv-leak-1 + Memcheck:Leak + fun:malloc_zone_malloc + fun:__setenv + fun:setenv +} +{ + mac-setenv-leak-2 + Memcheck:Leak + fun:malloc_zone_malloc + fun:malloc_set_zone_name + ... + fun:init__zone0 + fun:setenv +} +{ + mac-dyld-initializer-leak + Memcheck:Leak + fun:malloc + ... + fun:dyld_register_image_state_change_handler + fun:_dyld_initializer +} +{ + mac-tz-leak-1 + Memcheck:Leak + ... + fun:token_table_add + fun:notify_register_check + fun:notify_register_tz +} +{ + mac-tz-leak-2 + Memcheck:Leak + fun:malloc + fun:tzload +} +{ + mac-tz-leak-3 + Memcheck:Leak + fun:malloc + fun:tzsetwall_basic +} +{ + mac-tz-leak-4 + Memcheck:Leak + fun:malloc + fun:gmtsub +} +{ + mac-system-init-leak-1 + Memcheck:Leak + ... + fun:_libxpc_initializer + fun:libSystem_initializer +} +{ + mac-system-init-leak-2 + Memcheck:Leak + ... + fun:__keymgr_initializer + fun:libSystem_initializer +} +{ + mac-puts-leak + Memcheck:Leak + fun:malloc + fun:__smakebuf + ... + fun:puts +}