diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..1e432aeef --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,14 @@ +You are opening a _bug report_ against the libgit2 project. If you have a +question about an API or usage, please ask on StackOverflow: +http://stackoverflow.com/questions/tagged/libgit2. Please fill out the +reproduction steps (below) and delete this introductory paragraph. Thanks! + +### Reproduction steps + +### Expected behavior + +### Actual behavior + +### Version of libgit2 (release number or SHA1) + +### Operating system(s) tested diff --git a/.travis.yml b/.travis.yml index 2f3ffe355..af38252ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,9 +37,6 @@ matrix: - os: osx compiler: gcc include: - - compiler: i586-mingw32msvc-gcc - env: OPTIONS="-DCMAKE_TOOLCHAIN_FILE=../script/toolchain-mingw32.cmake" SKIP_TESTS=1 - os: linux - compiler: gcc env: COVERITY=1 os: linux @@ -50,9 +47,6 @@ matrix: os: linux allow_failures: - env: COVERITY=1 - - env: - - VALGRIND=1 - OPTIONS="-DBUILD_CLAR=ON -DBUILD_EXAMPLES=OFF -DDEBUG_POOL=ON -DCMAKE_BUILD_TYPE=Debug" install: - if [ "$TRAVIS_OS_NAME" = "osx" ]; then ./script/install-deps-${TRAVIS_OS_NAME}.sh; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 43476b99a..8544ac451 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -v0.24 + 1 +v0.25 + 1 ------- ### Changes or improvements @@ -9,11 +9,158 @@ v0.24 + 1 ### Breaking API changes +v0.25 +------- + +### Changes or improvements + +* Fix repository discovery with `git_repository_discover` and + `git_repository_open_ext` to match git's handling of a ceiling + directory at the current directory. git only checks ceiling + directories when its search ascends to a parent directory. A ceiling + directory matching the starting directory will not prevent git from + finding a repository in the starting directory or a parent directory. + +* Do not fail when deleting remotes in the presence of broken + global configs which contain branches. + +* Support for reading and writing git index v4 files + +* Improve the performance of the revwalk and bring us closer to git's code. + +* The reference db has improved support for concurrency and returns `GIT_ELOCKED` + when an operation could not be performed due to locking. + +* Nanosecond resolution is now activated by default, following git's change to + do this. + +* We now restrict the set of ciphers we let OpenSSL use by default. + +* Users can now register their own merge drivers for use with `.gitattributes`. + The library also gained built-in support for the union merge driver. + +* The default for creating references is now to validate that the object does + exist. + +* Add `git_proxy_options` which is used by the different networking + implementations to let the caller specify the proxy settings instead of + relying on the environment variables. + +### API additions + +* You can now get the user-agent used by libgit2 using the + `GIT_OPT_GET_USER_AGENT` option with `git_libgit2_opts()`. + It is the counterpart to `GIT_OPT_SET_USER_AGENT`. + +* The `GIT_OPT_SET_SSL_CIPHERS` option for `git_libgit2_opts()` lets you specify + a custom list of ciphers to use for OpenSSL. + +* `git_commit_create_buffer()` creates a commit and writes it into a + user-provided buffer instead of writing it into the object db. Combine it with + `git_commit_create_with_signature()` in order to create a commit with a + cryptographic signature. + +* `git_blob_create_fromstream()` and + `git_blob_create_fromstream_commit()` allow you to create a blob by + writing into a stream. Useful when you do not know the final size or + want to copy the contents from another stream. + +* New flags for `git_repository_open_ext`: + + * `GIT_REPOSITORY_OPEN_NO_DOTGIT` - Do not check for a repository by + appending `/.git` to the `start_path`; only open the repository if + `start_path` itself points to the git directory. + * `GIT_REPOSITORY_OPEN_FROM_ENV` - Find and open a git repository, + respecting the environment variables used by the git command-line + tools. If set, `git_repository_open_ext` will ignore the other + flags and the `ceiling_dirs` argument, and will allow a NULL + `path` to use `GIT_DIR` or search from the current directory. The + search for a repository will respect `$GIT_CEILING_DIRECTORIES` + and `$GIT_DISCOVERY_ACROSS_FILESYSTEM`. The opened repository + will respect `$GIT_INDEX_FILE`, `$GIT_NAMESPACE`, + `$GIT_OBJECT_DIRECTORY`, and `$GIT_ALTERNATE_OBJECT_DIRECTORIES`. + In the future, this flag will also cause `git_repository_open_ext` + to respect `$GIT_WORK_TREE` and `$GIT_COMMON_DIR`; currently, + `git_repository_open_ext` with this flag will error out if either + `$GIT_WORK_TREE` or `$GIT_COMMON_DIR` is set. + +* `git_diff_from_buffer()` can create a `git_diff` object from the contents + of a git-style patch file. + +* `git_index_version()` and `git_index_set_version()` to get and set + the index version + +* `git_odb_expand_ids()` lets you check for the existence of multiple + objects at once. + +* The new `git_blob_dup()`, `git_commit_dup()`, `git_tag_dup()` and + `git_tree_dup()` functions provide type-specific wrappers for + `git_object_dup()` to reduce noise and increase type safety for callers. + +* `git_reference_dup()` lets you duplicate a reference to aid in ownership + management and cleanup. + +* `git_signature_from_buffer()` lets you create a signature from a string in the + format that appear in objects. + +* `git_tree_create_updated()` lets you create a tree based on another one + together with a list of updates. For the covered update cases, it's more + efficient than the `git_index` route. + +* `git_apply_patch()` applies hunks from a `git_patch` to a buffer. + +* `git_diff_to_buf()` lets you print an entire diff directory to a buffer, + similar to how `git_patch_to_buf()` works. + +* `git_proxy_init_options()` is added to initialize a `git_proxy_options` + structure at run-time. + +* `git_merge_driver_register()`, `git_merge_driver_unregister()` let you + register and unregister a custom merge driver to be used when `.gitattributes` + specifies it. + +* `git_merge_driver_lookup()` can be used to look up a merge driver by name. + +* `git_merge_driver_source_repo()`, `git_merge_driver_source_ancestor()`, + `git_merge_driver_source_ours()`, `git_merge_driver_source_theirs()`, + `git_merge_driver_source_file_options()` added as accessors to + `git_merge_driver_source`. + +### API removals + +* `git_blob_create_fromchunks()` has been removed in favour of + `git_blob_create_fromstream()`. + +### Breaking API changes + +* `git_packbuilder_object_count` and `git_packbuilder_written` now + return a `size_t` instead of a `uint32_t` for more thorough + compatibility with the rest of the library. + +* `git_packbuiler_progress` now provides explicitly sized `uint32_t` + values instead of `unsigned int`. + +* `git_diff_file` now includes an `id_abbrev` field that reflects the + number of nibbles set in the `id` field. + +* `git_odb_backend` now has a `freshen` function pointer. This optional + function pointer is similar to the `exists` function, but it will update + a last-used marker. For filesystem-based object databases, this updates + the timestamp of the file containing the object, to indicate "freshness". + If this is `NULL`, then it will not be called and the `exists` function + will be used instead. + +* `git_remote_connect()` now accepts proxy options. + v0.24 ------- ### Changes or improvements +* Custom merge drivers can now be registered, which allows callers to + configure callbacks to honor `merge=driver` configuration in + `.gitattributes`. + * Custom filters can now be registered with wildcard attributes, for example `filter=*`. Consumers should examine the attributes parameter of the `check` function for details. @@ -80,6 +227,10 @@ v0.24 ### Breaking API changes +* `git_merge_options` now provides a `default_driver` that can be used + to provide the name of a merge driver to be used to handle files changed + during a merge. + * The `git_merge_tree_flag_t` is now `git_merge_flag_t`. Subsequently, its members are no longer prefixed with `GIT_MERGE_TREE_FLAG` but are now prefixed with `GIT_MERGE_FLAG`, and the `tree_flags` field of the @@ -111,12 +262,15 @@ v0.24 `GIT_CONFIG_LEVEL_PROGRAMDATA` which represent a rough Windows equivalent to the system level configuration. -* `git_rebase_init()` not also takes a merge options. +* `git_rebase_options` now has a `merge_options` field. * The index no longer performs locking itself. This is not something users of the library should have been relying on as it's not part of the concurrency guarantees. +* `git_remote_connect()` now takes a `custom_headers` argument to set + the extra HTTP header fields to send. + v0.23 ------ diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f9cff575..f26f46879 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Mo INCLUDE(CheckLibraryExists) INCLUDE(CheckFunctionExists) +INCLUDE(CheckSymbolExists) INCLUDE(CheckStructHasMember) INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgConfig) @@ -109,7 +110,7 @@ ELSE () ENDIF() IF (HAVE_STRUCT_STAT_NSEC OR WIN32) - OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" OFF ) + OPTION( USE_NSEC "Care about sub-second file mtimes and ctimes" ON ) ENDIF() # This variable will contain the libraries we need to put into @@ -507,6 +508,11 @@ ELSE () ENDIF () ENDIF() +CHECK_SYMBOL_EXISTS(regcomp_l "regex.h;xlocale.h" HAVE_REGCOMP_L) +IF (HAVE_REGCOMP_L) + ADD_DEFINITIONS(-DHAVE_REGCOMP_L) +ENDIF () + CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) IF (HAVE_FUTIMENS) ADD_DEFINITIONS(-DHAVE_FUTIMENS) @@ -589,8 +595,10 @@ IF (CMAKE_SIZEOF_VOID_P EQUAL 8) ADD_DEFINITIONS(-DGIT_ARCH_64) ELSEIF (CMAKE_SIZEOF_VOID_P EQUAL 4) ADD_DEFINITIONS(-DGIT_ARCH_32) +ELSEIF (CMAKE_SIZEOF_VOID_P) + MESSAGE(FATAL_ERROR "Unsupported architecture (pointer size is ${CMAKE_SIZEOF_VOID_P} bytes)") ELSE() - MESSAGE(FATAL_ERROR "Unsupported architecture") + MESSAGE(FATAL_ERROR "Unsupported architecture (CMAKE_SIZEOF_VOID_P is unset)") ENDIF() # Compile and link libgit2 @@ -697,6 +705,8 @@ IF (BUILD_CLAR) # Add a test target which runs the cred callback tests, to be # called after setting the url and user ADD_TEST(libgit2_clar-cred_callback libgit2_clar -v -sonline::clone::cred_callback) + ADD_TEST(libgit2_clar-proxy_credentials_in_url libgit2_clar -v -sonline::clone::proxy_credentials_in_url) + ADD_TEST(libgit2_clar-proxy_credentials_request libgit2_clar -v -sonline::clone::proxy_credentials_request) ENDIF () IF (TAGS) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 71fad63a9..28a143570 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -94,7 +94,7 @@ the change, but pass with your changes. In addition to new tests, please ensure that your changes do not cause any other test failures. Running the entire test suite is helpful before you submit a pull request. When you build libgit2, the test -suite will also be built. You can run all tests by simply running +suite will also be built. You can run most of the tests by simply running the resultant `libgit2_clar` binary. If you want to run a specific unit test, you can name it with the `-s` option. For example: @@ -105,6 +105,21 @@ worktree status tests: libgit2_clar -sstatus::worktree +The default test run is fairly exhaustive, but it will exclude some +unit tests by default: in particular, those that talk to network +servers and the tests that manipulate the filesystem in onerous +ways (and may need to have special privileges to run). To run the +network tests: + + libgit2_clar -ionline + +In addition, various tests may be enabled by environment variables, +like the ones that write exceptionally large repositories or manipulate +the filesystem structure in unexpected ways. These tests *may be +dangerous* to run on a normal machine and may harm your filesystem. It's +not recommended that you run these; instead, the continuous integration +servers will run these (in a sandbox). + ## Porting Code From Other Open-Source Projects `libgit2` is licensed under the terms of the GPL v2 with a linking diff --git a/Makefile.embed b/Makefile.embed deleted file mode 100644 index eb8a78ebf..000000000 --- a/Makefile.embed +++ /dev/null @@ -1,60 +0,0 @@ -PLATFORM=$(shell uname -s) - -ifneq (,$(CROSS_COMPILE)) - PREFIX=$(CROSS_COMPILE)- -else - PREFIX= -endif - -MINGW=0 -ifneq (,$(findstring MINGW32,$(PLATFORM))) - MINGW=1 -endif -ifneq (,$(findstring mingw,$(CROSS_COMPILE))) - MINGW=1 -endif - -rm=rm -f -AR=$(PREFIX)ar cq -RANLIB=$(PREFIX)ranlib - -LIBNAME=libgit2.a - -ifeq ($(MINGW),1) - CC=gcc -else - CC=cc -endif - -CC:=$(PREFIX)$(CC) - -INCLUDES= -I. -Isrc -Iinclude -Ideps/http-parser -Ideps/zlib - -DEFINES= $(INCLUDES) -DNO_VIZ -DSTDC -DNO_GZIP -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE $(EXTRA_DEFINES) -CFLAGS= -g $(DEFINES) -Wall -Wextra -Wno-missing-field-initializers -O2 $(EXTRA_CFLAGS) - -SRCS = $(wildcard src/*.c) $(wildcard src/transports/*.c) $(wildcard src/xdiff/*.c) $(wildcard deps/http-parser/*.c) $(wildcard deps/zlib/*.c) src/hash/hash_generic.c - -ifeq ($(MINGW),1) - SRCS += $(wildcard src/win32/*.c) $(wildcard src/compat/*.c) deps/regex/regex.c - INCLUDES += -Ideps/regex - DEFINES += -DWIN32 -D_WIN32_WINNT=0x0501 -D__USE_MINGW_ANSI_STDIO=1 -else - SRCS += $(wildcard src/unix/*.c) - CFLAGS += -fPIC -endif - -OBJS = $(patsubst %.c,%.o,$(SRCS)) - -%.c.o: - $(CC) $(CFLAGS) -c $*.c - -all: $(LIBNAME) - -$(LIBNAME): $(OBJS) - $(rm) $@ - $(AR) $@ $(OBJS) - $(RANLIB) $@ - -clean: - $(rm) $(OBJS) $(LIBNAME) diff --git a/PROJECTS.md b/PROJECTS.md index 87ce78f02..f53a2e120 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -5,7 +5,7 @@ So, you want to start helping out with `libgit2`? That's fantastic! We welcome contributions and we promise we'll try to be nice. This is a list of libgit2 related projects that new contributors can take -on. It includes a number of good starter projects and well as some larger +on. It includes a number of good starter projects as well as some larger ideas that no one is actively working on. ## Before You Start @@ -70,11 +70,11 @@ some incremental steps listed towards the larger goal. Those steps might make good smaller projects by themselves. * Port part of the Git test suite to run against the command line emulation - in examples/ - * Pick a Git command that is emulated in our examples/ area + in `examples/` + * Pick a Git command that is emulated in our `examples/` area * Extract the Git tests that exercise that command * Convert the tests to call our emulation - * These tests could go in examples/tests/... + * These tests could go in `examples/tests/`... * Add hooks API to enumerate and manage hooks (not run them at this point) * Enumeration of available hooks * Lookup API to see which hooks have a script and get the script diff --git a/README.md b/README.md index b69a18a38..19d24dea1 100644 --- a/README.md +++ b/README.md @@ -43,9 +43,17 @@ We ask that you not open a GitHub Issue for help, only for bug reports. What It Can Do ============== -`libgit2` is already very usable and is being used in production for many -applications including the GitHub.com site, in Plastic SCM and also powering -Microsoft's Visual Studio tools for Git. The library provides: +The goal of this library is to allow its users the ability to handle Git data in +their applications from their programming language of choice, as is used in +production for many applications including the GitHub.com site, in Plastic SCM +and also powering Microsoft's Visual Studio tools for Git. + +It does not aim to replace the git tool or its user-facing commands. Some APIs +resemble the plumbing commands as those align closely with the concepts of the +Git system, but most commands a user would type are out of scope for this +library to implement directly. + +The library provides: * SHA conversions, formatting and shortening * abstracted ODB backend system @@ -190,7 +198,7 @@ Language Bindings Here are the bindings to libgit2 that are currently available: * C++ - * libqgit2, Qt bindings + * libqgit2, Qt bindings * Chicken Scheme * chicken-git * D @@ -214,7 +222,6 @@ Here are the bindings to libgit2 that are currently available: * .NET * libgit2sharp * Node.js - * node-gitteh * nodegit * Objective-C * objective-git @@ -227,7 +234,7 @@ Here are the bindings to libgit2 that are currently available: * PHP * php-git * PowerShell - * GitPowerShell + * PSGit * Python * pygit2 * R @@ -237,7 +244,7 @@ Here are the bindings to libgit2 that are currently available: * Rust * git2-rs * Swift - * Gift + * SwiftGit2 * Vala * libgit2.vapi diff --git a/THREADING.md b/THREADING.md index 0b9e50286..430bca858 100644 --- a/THREADING.md +++ b/THREADING.md @@ -62,33 +62,38 @@ general case still affects you if you use ssh. General Case ------------ -By default we use libcurl, which has its own ![recommendations for -thread safety](http://curl.haxx.se/libcurl/c/libcurl-tutorial.html#Multi-threading). +If it's available, by default we use libcurl to provide HTTP tunneling support, +which may be linked against a number of cryptographic libraries and has its +own +[recommendations for thread safety](https://curl.haxx.se/libcurl/c/threadsafe.html). -If libcurl was not found or was disabled, libgit2 uses OpenSSL to be -able to use HTTPS as a transport. This library is made to be -thread-implementation agnostic, and the users of the library must set -which locking function it should use. This means that libgit2 cannot -know what to set as the user of libgit2 may use OpenSSL independently -and the locking settings must survive libgit2 shutting down. +If there are no alternative TLS implementations (currently only +SecureTransport), libgit2 uses OpenSSL in order to use HTTPS as a transport. +OpenSSL is thread-safe starting at version 1.1.0. If your copy of libgit2 is +linked against that version, you do not need to take any further steps. -Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used -by libssh2 depending on the configuration. If OpenSSL is used both by -libgit2 and libssh2, you only need to set up threading for OpenSSL once. +Older versions of OpenSSL are made to be thread-implementation agnostic, and the +users of the library must set which locking function it should use. libgit2 +cannot know what to set as the user of libgit2 may also be using OpenSSL independently and +the locking settings must then live outside the lifetime of libgit2. -libgit2 does provide a last-resort convenience function +Even if libgit2 doesn't use OpenSSL directly, OpenSSL can still be used by +libssh2 or libcurl depending on the configuration. If OpenSSL is used by +more than one library, you only need to set up threading for OpenSSL once. + +If libgit2 is linked against OpenSSL, it provides a last-resort convenience function `git_openssl_set_locking()` (available in `sys/openssl.h`) to use the -platform-native mutex mechanisms to perform the locking, which you may -rely on if you do not want to use OpenSSL outside of libgit2, or you -know that libgit2 will outlive the rest of the operations. It is not +platform-native mutex mechanisms to perform the locking, which you can use +if you do not want to use OpenSSL outside of libgit2, or you +know that libgit2 will outlive the rest of the operations. It is then not safe to use OpenSSL multi-threaded after libgit2's shutdown function has been called. Note `git_openssl_set_locking()` only works if libgit2 uses OpenSSL directly - if OpenSSL is only used as a dependency -of libssh2 as described above, `git_openssl_set_locking()` is a no-op. +of libssh2 or libcurl as described above, `git_openssl_set_locking()` is a no-op. If your programming language offers a package/bindings for OpenSSL, you should very strongly prefer to use that in order to set up -locking, as they provide a level of coördination which is impossible +locking, as they provide a level of coordination which is impossible when using this function. See the @@ -96,13 +101,10 @@ See the on threading for more details, and http://trac.libssh2.org/wiki/MultiThreading for a specific example of providing the threading callbacks. -Be also aware that libgit2 does not always link against OpenSSL -if there are alternatives provided by the system. - libssh2 may be linked against OpenSSL or libgcrypt. If it uses OpenSSL, see the above paragraphs. If it uses libgcrypt, then you need to set up its locking before using it multi-threaded. libgit2 has no -direct connection to libgcrypt and thus has not convenience functions for +direct connection to libgcrypt and thus has no convenience functions for it (but libgcrypt has macros). Read libgcrypt's [threading documentation for more information](http://www.gnupg.org/documentation/manuals/gcrypt/Multi_002dThreading.html) diff --git a/appveyor.yml b/appveyor.yml index 3ed3c49a1..9e94c0726 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,19 +12,14 @@ environment: ARCH: 32 - GENERATOR: "Visual Studio 11 Win64" ARCH: 64 - - GENERATOR: "MSYS Makefiles" - ARCH: 32 - GENERATOR: "MSYS Makefiles" ARCH: i686 # this is for 32-bit MinGW-w64 - GENERATOR: "MSYS Makefiles" ARCH: 64 -matrix: - allow_failures: - - GENERATOR: "MSYS Makefiles" - ARCH: 32 cache: - i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z - x86_64-4.9.2-release-win32-seh-rt_v3-rev1.7z + build_script: - ps: | mkdir build @@ -37,7 +32,18 @@ build_script: if "%GENERATOR%"=="MSYS Makefiles" (C:\MinGW\msys\1.0\bin\sh --login /c/projects/libgit2/script/appveyor-mingw.sh) test_script: - ps: | + $ErrorActionPreference="Stop" + Start-FileDownload https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar -FileName poxyproxy.jar + # Run this early so we know it's ready by the time we need it + $proxyJob = Start-Job { java -jar $Env:APPVEYOR_BUILD_FOLDER\build\poxyproxy.jar -d --port 8080 --credentials foo:bar } ctest -V -R libgit2_clar $env:GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" $env:GITTEST_REMOTE_USER="libgit2test" ctest -V -R libgit2_clar-cred_callback + Receive-Job -Job $proxyJob + $env:GITTEST_REMOTE_PROXY_URL = "http://foo:bar@localhost:8080" + ctest -V -R libgit2_clar-proxy_credentials_in_url + $env:GITTEST_REMOTE_PROXY_URL = "http://localhost:8080" + $env:GITTEST_REMOTE_PROXY_USER = "foo" + $env:GITTEST_REMOTE_PROXY_PASS = "bar" + ctest -V -R libgit2_clar-proxy_credentials_request diff --git a/examples/add.c b/examples/add.c index 0101ab9ae..e5849892e 100644 --- a/examples/add.c +++ b/examples/add.c @@ -75,15 +75,14 @@ int print_matched_cb(const char *path, const char *matched_pathspec, void *paylo { struct print_payload p = *(struct print_payload*)(payload); int ret; - git_status_t status; + unsigned status; (void)matched_pathspec; - if (git_status_file((unsigned int*)(&status), p.repo, path)) { - return -1; //abort + if (git_status_file(&status, p.repo, path)) { + return -1; } - if (status & GIT_STATUS_WT_MODIFIED || - status & GIT_STATUS_WT_NEW) { + if (status & GIT_STATUS_WT_MODIFIED || status & GIT_STATUS_WT_NEW) { printf("add '%s'\n", path); ret = 0; } else { diff --git a/examples/common.c b/examples/common.c index 0f25f3787..96f5eaa8e 100644 --- a/examples/common.c +++ b/examples/common.c @@ -146,6 +146,25 @@ int match_uint16_arg( return 1; } +int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt) +{ + const char *found = match_numeric_arg(args, opt); + uint16_t val; + char *endptr = NULL; + + if (!found) + return 0; + + val = (uint32_t)strtoul(found, &endptr, 0); + if (!endptr || *endptr != '\0') + fatal("expected number after argument", opt); + + if (out) + *out = val; + return 1; +} + static int match_int_internal( int *out, const char *str, int allow_negative, const char *opt) { diff --git a/examples/common.h b/examples/common.h index b9fa37ce9..adea0d318 100644 --- a/examples/common.h +++ b/examples/common.h @@ -72,6 +72,15 @@ extern int match_str_arg( extern int match_uint16_arg( uint16_t *out, struct args_info *args, const char *opt); +/** + * Check current `args` entry against `opt` string parsing as uint32. If + * `opt` matches exactly, take the next arg as a uint16_t value; if `opt` + * is a prefix (equal sign optional), take the remainder of the arg as a + * uint32_t value; otherwise return 0. + */ +extern int match_uint32_arg( + uint32_t *out, struct args_info *args, const char *opt); + /** * Check current `args` entry against `opt` string parsing as int. If * `opt` matches exactly, take the next arg as an int value; if it matches diff --git a/examples/diff.c b/examples/diff.c index b69cb2218..9a4f7a59f 100644 --- a/examples/diff.c +++ b/examples/diff.c @@ -293,11 +293,11 @@ static void parse_opts(struct opts *o, int argc, char *argv[]) else if (is_prefixed(a, "-B") || is_prefixed(a, "--break-rewrites")) /* TODO: parse thresholds */ o->findopts.flags |= GIT_DIFF_FIND_REWRITES; - else if (!match_uint16_arg( + else if (!match_uint32_arg( &o->diffopts.context_lines, &args, "-U") && - !match_uint16_arg( + !match_uint32_arg( &o->diffopts.context_lines, &args, "--unified") && - !match_uint16_arg( + !match_uint32_arg( &o->diffopts.interhunk_lines, &args, "--inter-hunk-context") && !match_uint16_arg( &o->diffopts.id_abbrev, &args, "--abbrev") && diff --git a/examples/general.c b/examples/general.c index 706650b67..7c05d8712 100644 --- a/examples/general.c +++ b/examples/general.c @@ -12,39 +12,58 @@ * . */ -// [**libgit2**][lg] is a portable, pure C implementation of the Git core -// methods provided as a re-entrant linkable library with a solid API, -// allowing you to write native speed custom Git applications in any -// language which supports C bindings. -// -// This file is an example of using that API in a real, compilable C file. -// As the API is updated, this file will be updated to demonstrate the new -// functionality. -// -// If you're trying to write something in C using [libgit2][lg], you should -// also check out the generated [API documentation][ap]. We try to link to -// the relevant sections of the API docs in each section in this file. -// -// **libgit2** (for the most part) only implements the core plumbing -// functions, not really the higher level porcelain stuff. For a primer on -// Git Internals that you will need to know to work with Git at this level, -// check out [Chapter 9][pg] of the Pro Git book. -// -// [lg]: http://libgit2.github.com -// [ap]: http://libgit2.github.com/libgit2 -// [pg]: http://progit.org/book/ch9-0.html +/** + * [**libgit2**][lg] is a portable, pure C implementation of the Git core + * methods provided as a re-entrant linkable library with a solid API, + * allowing you to write native speed custom Git applications in any + * language which supports C bindings. + * + * This file is an example of using that API in a real, compilable C file. + * As the API is updated, this file will be updated to demonstrate the new + * functionality. + * + * If you're trying to write something in C using [libgit2][lg], you should + * also check out the generated [API documentation][ap]. We try to link to + * the relevant sections of the API docs in each section in this file. + * + * **libgit2** (for the most part) only implements the core plumbing + * functions, not really the higher level porcelain stuff. For a primer on + * Git Internals that you will need to know to work with Git at this level, + * check out [Chapter 10][pg] of the Pro Git book. + * + * [lg]: http://libgit2.github.com + * [ap]: http://libgit2.github.com/libgit2 + * [pg]: https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain + */ -// ### Includes - -// Including the `git2.h` header will include all the other libgit2 headers -// that you need. It should be the only thing you need to include in order -// to compile properly and get all the libgit2 API. +/** + * ### Includes + * + * Including the `git2.h` header will include all the other libgit2 headers + * that you need. It should be the only thing you need to include in order + * to compile properly and get all the libgit2 API. + */ #include #include +#include -// Almost all libgit2 functions return 0 on success or negative on error. -// This is not production quality error checking, but should be sufficient -// as an example. +static void oid_parsing(git_oid *out); +static void object_database(git_repository *repo, git_oid *oid); +static void commit_writing(git_repository *repo); +static void commit_parsing(git_repository *repo); +static void tag_parsing(git_repository *repo); +static void tree_parsing(git_repository *repo); +static void blob_parsing(git_repository *repo); +static void revwalking(git_repository *repo); +static void index_walking(git_repository *repo); +static void reference_listing(git_repository *repo); +static void config_files(const char *repo_path); + +/** + * Almost all libgit2 functions return 0 on success or negative on error. + * This is not production quality error checking, but should be sufficient + * as an example. + */ static void check_error(int error_code, const char *action) { const git_error *error = giterr_last(); @@ -52,480 +71,645 @@ static void check_error(int error_code, const char *action) return; printf("Error %d %s - %s\n", error_code, action, - (error && error->message) ? error->message : "???"); + (error && error->message) ? error->message : "???"); exit(1); } int main (int argc, char** argv) { - // Initialize the library, this will set up any global state which libgit2 needs - // including threading and crypto - git_libgit2_init(); - - // ### Opening the Repository - - // There are a couple of methods for opening a repository, this being the - // simplest. There are also [methods][me] for specifying the index file - // and work tree locations, here we assume they are in the normal places. - // - // (Try running this program against tests/resources/testrepo.git.) - // - // [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository - int error; - const char *repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; - git_repository *repo; - - error = git_repository_open(&repo, repo_path); - check_error(error, "opening repository"); - - // ### SHA-1 Value Conversions - - // For our first example, we will convert a 40 character hex value to the - // 20 byte raw SHA1 value. - printf("*Hex to Raw*\n"); - char hex[] = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"; - - // The `git_oid` is the structure that keeps the SHA value. We will use - // this throughout the example for storing the value of the current SHA - // key we're working with. - git_oid oid; - git_oid_fromstr(&oid, hex); - - // Once we've converted the string into the oid value, we can get the raw - // value of the SHA by accessing `oid.id` - - // Next we will convert the 20 byte raw SHA1 value to a human readable 40 - // char hex value. - printf("\n*Raw to Hex*\n"); - char out[GIT_OID_HEXSZ+1]; - out[GIT_OID_HEXSZ] = '\0'; - - // If you have a oid, you can easily get the hex value of the SHA as well. - git_oid_fmt(out, &oid); - printf("SHA hex string: %s\n", out); - - // ### Working with the Object Database - - // **libgit2** provides [direct access][odb] to the object database. The - // object database is where the actual objects are stored in Git. For - // working with raw objects, we'll need to get this structure from the - // repository. - // - // [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb - git_odb *odb; - git_repository_odb(&odb, repo); - - // #### Raw Object Reading - - printf("\n*Raw Object Read*\n"); - git_odb_object *obj; - git_otype otype; - const unsigned char *data; - const char *str_type; - - // We can read raw objects directly from the object database if we have - // the oid (SHA) of the object. This allows us to access objects without - // knowing their type and inspect the raw bytes unparsed. - error = git_odb_read(&obj, odb, &oid); - check_error(error, "finding object in repository"); - - // A raw object only has three properties - the type (commit, blob, tree - // or tag), the size of the raw data and the raw, unparsed data itself. - // For a commit or tag, that raw data is human readable plain ASCII - // text. For a blob it is just file contents, so it could be text or - // binary data. For a tree it is a special binary format, so it's unlikely - // to be hugely helpful as a raw object. - data = (const unsigned char *)git_odb_object_data(obj); - otype = git_odb_object_type(obj); - - // We provide methods to convert from the object type which is an enum, to - // a string representation of that value (and vice-versa). - str_type = git_object_type2string(otype); - printf("object length and type: %d, %s\n", - (int)git_odb_object_size(obj), - str_type); - - // For proper memory management, close the object when you are done with - // it or it will leak memory. - git_odb_object_free(obj); - - // #### Raw Object Writing - - printf("\n*Raw Object Write*\n"); - - // You can also write raw object data to Git. This is pretty cool because - // it gives you direct access to the key/value properties of Git. Here - // we'll write a new blob object that just contains a simple string. - // Notice that we have to specify the object type as the `git_otype` enum. - git_odb_write(&oid, odb, "test data", sizeof("test data") - 1, GIT_OBJ_BLOB); - - // Now that we've written the object, we can check out what SHA1 was - // generated when the object was written to our database. - git_oid_fmt(out, &oid); - printf("Written Object: %s\n", out); - - // ### Object Parsing - - // libgit2 has methods to parse every object type in Git so you don't have - // to work directly with the raw data. This is much faster and simpler - // than trying to deal with the raw data yourself. - - // #### Commit Parsing - - // [Parsing commit objects][pco] is simple and gives you access to all the - // data in the commit - the author (name, email, datetime), committer - // (same), tree, message, encoding and parent(s). - // - // [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit - - printf("\n*Commit Parsing*\n"); - - git_commit *commit; - git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479"); - - error = git_commit_lookup(&commit, repo, &oid); - check_error(error, "looking up commit"); - - const git_signature *author, *cmtter; - const char *message; - time_t ctime; - unsigned int parents, p; - - // Each of the properties of the commit object are accessible via methods, - // including commonly needed variations, such as `git_commit_time` which - // returns the author time and `git_commit_message` which gives you the - // commit message (as a NUL-terminated string). - message = git_commit_message(commit); - author = git_commit_author(commit); - cmtter = git_commit_committer(commit); - ctime = git_commit_time(commit); - - // The author and committer methods return [git_signature] structures, - // which give you name, email and `when`, which is a `git_time` structure, - // giving you a timestamp and timezone offset. - printf("Author: %s (%s)\n", author->name, author->email); - - // Commits can have zero or more parents. The first (root) commit will - // have no parents, most commits will have one (i.e. the commit it was - // based on) and merge commits will have two or more. Commits can - // technically have any number, though it's rare to have more than two. - parents = git_commit_parentcount(commit); - for (p = 0;p < parents;p++) { - git_commit *parent; - git_commit_parent(&parent, commit, p); - git_oid_fmt(out, git_commit_id(parent)); - printf("Parent: %s\n", out); - git_commit_free(parent); - } - - // Don't forget to close the object to prevent memory leaks. You will have - // to do this for all the objects you open and parse. - git_commit_free(commit); - - // #### Writing Commits - - // libgit2 provides a couple of methods to create commit objects easily as - // well. There are four different create signatures, we'll just show one - // of them here. You can read about the other ones in the [commit API - // docs][cd]. - // - // [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit - - printf("\n*Commit Writing*\n"); - git_oid tree_id, parent_id, commit_id; - git_tree *tree; - git_commit *parent; - - // Creating signatures for an authoring identity and time is simple. You - // will need to do this to specify who created a commit and when. Default - // values for the name and email should be found in the `user.name` and - // `user.email` configuration options. See the `config` section of this - // example file to see how to access config values. - git_signature_new((git_signature **)&author, - "Scott Chacon", "schacon@gmail.com", 123456789, 60); - git_signature_new((git_signature **)&cmtter, - "Scott A Chacon", "scott@github.com", 987654321, 90); - - // Commit objects need a tree to point to and optionally one or more - // parents. Here we're creating oid objects to create the commit with, - // but you can also use - git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1"); - git_tree_lookup(&tree, repo, &tree_id); - git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - git_commit_lookup(&parent, repo, &parent_id); - - // Here we actually create the commit object with a single call with all - // the values we need to create the commit. The SHA key is written to the - // `commit_id` variable here. - git_commit_create_v( - &commit_id, /* out id */ - repo, - NULL, /* do not update the HEAD */ - author, - cmtter, - NULL, /* use default message encoding */ - "example commit", - tree, - 1, parent); - - // Now we can take a look at the commit SHA we've generated. - git_oid_fmt(out, &commit_id); - printf("New Commit: %s\n", out); - - // #### Tag Parsing - - // You can parse and create tags with the [tag management API][tm], which - // functions very similarly to the commit lookup, parsing and creation - // methods, since the objects themselves are very similar. - // - // [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag - printf("\n*Tag Parsing*\n"); - git_tag *tag; - const char *tmessage, *tname; - git_otype ttype; - - // We create an oid for the tag object if we know the SHA and look it up - // the same way that we would a commit (or any other object). - git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); - - error = git_tag_lookup(&tag, repo, &oid); - check_error(error, "looking up tag"); - - // Now that we have the tag object, we can extract the information it - // generally contains: the target (usually a commit object), the type of - // the target object (usually 'commit'), the name ('v1.0'), the tagger (a - // git_signature - name, email, timestamp), and the tag message. - git_tag_target((git_object **)&commit, tag); - tname = git_tag_name(tag); // "test" - ttype = git_tag_target_type(tag); // GIT_OBJ_COMMIT (otype enum) - tmessage = git_tag_message(tag); // "tag message\n" - printf("Tag Message: %s\n", tmessage); - - git_commit_free(commit); - - // #### Tree Parsing - - // [Tree parsing][tp] is a bit different than the other objects, in that - // we have a subtype which is the tree entry. This is not an actual - // object type in Git, but a useful structure for parsing and traversing - // tree entries. - // - // [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree - printf("\n*Tree Parsing*\n"); - - const git_tree_entry *entry; - git_object *objt; - - // Create the oid and lookup the tree object just like the other objects. - git_oid_fromstr(&oid, "2a741c18ac5ff082a7caaec6e74db3075a1906b5"); - git_tree_lookup(&tree, repo, &oid); - - // Getting the count of entries in the tree so you can iterate over them - // if you want to. - size_t cnt = git_tree_entrycount(tree); // 3 - printf("tree entries: %d\n", (int)cnt); - - entry = git_tree_entry_byindex(tree, 0); - printf("Entry name: %s\n", git_tree_entry_name(entry)); // "hello.c" - - // You can also access tree entries by name if you know the name of the - // entry you're looking for. - entry = git_tree_entry_byname(tree, "README"); - git_tree_entry_name(entry); // "hello.c" - - // Once you have the entry object, you can access the content or subtree - // (or commit, in the case of submodules) that it points to. You can also - // get the mode if you want. - git_tree_entry_to_object(&objt, repo, entry); // blob - - // Remember to close the looked-up object once you are done using it - git_object_free(objt); - - // #### Blob Parsing - - // The last object type is the simplest and requires the least parsing - // help. Blobs are just file contents and can contain anything, there is - // no structure to it. The main advantage to using the [simple blob - // api][ba] is that when you're creating blobs you don't have to calculate - // the size of the content. There is also a helper for reading a file - // from disk and writing it to the db and getting the oid back so you - // don't have to do all those steps yourself. - // - // [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob - - printf("\n*Blob Parsing*\n"); - git_blob *blob; - - git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08"); - git_blob_lookup(&blob, repo, &oid); - - // You can access a buffer with the raw contents of the blob directly. - // Note that this buffer may not be contain ASCII data for certain blobs - // (e.g. binary files): do not consider the buffer a NULL-terminated - // string, and use the `git_blob_rawsize` attribute to find out its exact - // size in bytes - printf("Blob Size: %ld\n", (long)git_blob_rawsize(blob)); // 8 - git_blob_rawcontent(blob); // "content" - - // ### Revwalking - - // The libgit2 [revision walking api][rw] provides methods to traverse the - // directed graph created by the parent pointers of the commit objects. - // Since all commits point back to the commit that came directly before - // them, you can walk this parentage as a graph and find all the commits - // that were ancestors of (reachable from) a given starting point. This - // can allow you to create `git log` type functionality. - // - // [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk - - printf("\n*Revwalking*\n"); - git_revwalk *walk; - git_commit *wcommit; - - git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); - - // To use the revwalker, create a new walker, tell it how you want to sort - // the output and then push one or more starting points onto the walker. - // If you want to emulate the output of `git log` you would push the SHA - // of the commit that HEAD points to into the walker and then start - // traversing them. You can also 'hide' commits that you want to stop at - // or not see any of their ancestors. So if you want to emulate `git log - // branch1..branch2`, you would push the oid of `branch2` and hide the oid - // of `branch1`. - git_revwalk_new(&walk, repo); - git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); - git_revwalk_push(walk, &oid); - - const git_signature *cauth; - const char *cmsg; - - // Now that we have the starting point pushed onto the walker, we start - // asking for ancestors. It will return them in the sorting order we asked - // for as commit oids. We can then lookup and parse the committed pointed - // at by the returned OID; note that this operation is specially fast - // since the raw contents of the commit object will be cached in memory - while ((git_revwalk_next(&oid, walk)) == 0) { - error = git_commit_lookup(&wcommit, repo, &oid); - check_error(error, "looking up commit during revwalk"); - - cmsg = git_commit_message(wcommit); - cauth = git_commit_author(wcommit); - printf("%s (%s)\n", cmsg, cauth->email); - - git_commit_free(wcommit); - } - - // Like the other objects, be sure to free the revwalker when you're done - // to prevent memory leaks. Also, make sure that the repository being - // walked it not deallocated while the walk is in progress, or it will - // result in undefined behavior - git_revwalk_free(walk); - - // ### Index File Manipulation - - // The [index file API][gi] allows you to read, traverse, update and write - // the Git index file (sometimes thought of as the staging area). - // - // [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index - - printf("\n*Index Walking*\n"); - - git_index *index; - unsigned int i, ecount; - - // You can either open the index from the standard location in an open - // repository, as we're doing here, or you can open and manipulate any - // index file with `git_index_open_bare()`. The index for the repository - // will be located and loaded from disk. - git_repository_index(&index, repo); - - // For each entry in the index, you can get a bunch of information - // including the SHA (oid), path and mode which map to the tree objects - // that are written out. It also has filesystem properties to help - // determine what to inspect for changes (ctime, mtime, dev, ino, uid, - // gid, file_size and flags) All these properties are exported publicly in - // the `git_index_entry` struct - ecount = git_index_entrycount(index); - for (i = 0; i < ecount; ++i) { - const git_index_entry *e = git_index_get_byindex(index, i); - - printf("path: %s\n", e->path); - printf("mtime: %d\n", (int)e->mtime.seconds); - printf("fs: %d\n", (int)e->file_size); - } - - git_index_free(index); - - // ### References - - // The [reference API][ref] allows you to list, resolve, create and update - // references such as branches, tags and remote references (everything in - // the .git/refs directory). - // - // [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference - - printf("\n*Reference Listing*\n"); - - // Here we will implement something like `git for-each-ref` simply listing - // out all available references and the object SHA they resolve to. - git_strarray ref_list; - git_reference_list(&ref_list, repo); - - const char *refname; - git_reference *ref; - - // Now that we have the list of reference names, we can lookup each ref - // one at a time and resolve them to the SHA, then print both values out. - for (i = 0; i < ref_list.count; ++i) { - refname = ref_list.strings[i]; - git_reference_lookup(&ref, repo, refname); - - switch (git_reference_type(ref)) { - case GIT_REF_OID: - git_oid_fmt(out, git_reference_target(ref)); - printf("%s [%s]\n", refname, out); - break; - - case GIT_REF_SYMBOLIC: - printf("%s => %s\n", refname, git_reference_symbolic_target(ref)); - break; - default: - fprintf(stderr, "Unexpected reference type\n"); - exit(1); - } - } - - git_strarray_free(&ref_list); - - // ### Config Files - - // The [config API][config] allows you to list and updatee config values - // in any of the accessible config file locations (system, global, local). - // - // [config]: http://libgit2.github.com/libgit2/#HEAD/group/config - - printf("\n*Config Listing*\n"); - - const char *email; - int32_t j; - - git_config *cfg; - - // Open a config object so we can read global values from it. - char config_path[256]; - sprintf(config_path, "%s/config", repo_path); - check_error(git_config_open_ondisk(&cfg, config_path), "opening config"); - - git_config_get_int32(&j, cfg, "help.autocorrect"); - printf("Autocorrect: %d\n", j); - - git_config_get_string(&email, cfg, "user.email"); - printf("Email: %s\n", email); - - // Finally, when you're done with the repository, you can free it as well. - git_repository_free(repo); - - return 0; + int error; + git_oid oid; + char *repo_path; + git_repository *repo; + + /** + * Initialize the library, this will set up any global state which libgit2 needs + * including threading and crypto + */ + git_libgit2_init(); + + /** + * ### Opening the Repository + * + * There are a couple of methods for opening a repository, this being the + * simplest. There are also [methods][me] for specifying the index file + * and work tree locations, here we assume they are in the normal places. + * + * (Try running this program against tests/resources/testrepo.git.) + * + * [me]: http://libgit2.github.com/libgit2/#HEAD/group/repository + */ + repo_path = (argc > 1) ? argv[1] : "/opt/libgit2-test/.git"; + + error = git_repository_open(&repo, repo_path); + check_error(error, "opening repository"); + + oid_parsing(&oid); + object_database(repo, &oid); + commit_writing(repo); + commit_parsing(repo); + tag_parsing(repo); + tree_parsing(repo); + blob_parsing(repo); + revwalking(repo); + index_walking(repo); + reference_listing(repo); + config_files(repo_path); + + /** + * Finally, when you're done with the repository, you can free it as well. + */ + git_repository_free(repo); + + return 0; } +/** + * ### SHA-1 Value Conversions + */ +static void oid_parsing(git_oid *oid) +{ + char out[GIT_OID_HEXSZ+1]; + char hex[] = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"; + + printf("*Hex to Raw*\n"); + + /** + * For our first example, we will convert a 40 character hex value to the + * 20 byte raw SHA1 value. + * + * The `git_oid` is the structure that keeps the SHA value. We will use + * this throughout the example for storing the value of the current SHA + * key we're working with. + */ + git_oid_fromstr(oid, hex); + + // Once we've converted the string into the oid value, we can get the raw + // value of the SHA by accessing `oid.id` + + // Next we will convert the 20 byte raw SHA1 value to a human readable 40 + // char hex value. + printf("\n*Raw to Hex*\n"); + out[GIT_OID_HEXSZ] = '\0'; + + /** + * If you have a oid, you can easily get the hex value of the SHA as well. + */ + git_oid_fmt(out, oid); + + /** + * If you have a oid, you can easily get the hex value of the SHA as well. + */ + git_oid_fmt(out, oid); + printf("SHA hex string: %s\n", out); +} + +/** + * ### Working with the Object Database + * + * **libgit2** provides [direct access][odb] to the object database. The + * object database is where the actual objects are stored in Git. For + * working with raw objects, we'll need to get this structure from the + * repository. + * + * [odb]: http://libgit2.github.com/libgit2/#HEAD/group/odb + */ +static void object_database(git_repository *repo, git_oid *oid) +{ + char oid_hex[GIT_OID_HEXSZ+1] = { 0 }; + const unsigned char *data; + const char *str_type; + int error; + git_odb_object *obj; + git_odb *odb; + git_otype otype; + + git_repository_odb(&odb, repo); + + /** + * #### Raw Object Reading + */ + + printf("\n*Raw Object Read*\n"); + + /** + * We can read raw objects directly from the object database if we have + * the oid (SHA) of the object. This allows us to access objects without + * knowing their type and inspect the raw bytes unparsed. + */ + error = git_odb_read(&obj, odb, oid); + check_error(error, "finding object in repository"); + + /** + * A raw object only has three properties - the type (commit, blob, tree + * or tag), the size of the raw data and the raw, unparsed data itself. + * For a commit or tag, that raw data is human readable plain ASCII + * text. For a blob it is just file contents, so it could be text or + * binary data. For a tree it is a special binary format, so it's unlikely + * to be hugely helpful as a raw object. + */ + data = (const unsigned char *)git_odb_object_data(obj); + otype = git_odb_object_type(obj); + + /** + * We provide methods to convert from the object type which is an enum, to + * a string representation of that value (and vice-versa). + */ + str_type = git_object_type2string(otype); + printf("object length and type: %d, %s\nobject data: %s\n", + (int)git_odb_object_size(obj), + str_type, data); + + /** + * For proper memory management, close the object when you are done with + * it or it will leak memory. + */ + git_odb_object_free(obj); + + /** + * #### Raw Object Writing + */ + + printf("\n*Raw Object Write*\n"); + + /** + * You can also write raw object data to Git. This is pretty cool because + * it gives you direct access to the key/value properties of Git. Here + * we'll write a new blob object that just contains a simple string. + * Notice that we have to specify the object type as the `git_otype` enum. + */ + git_odb_write(oid, odb, "test data", sizeof("test data") - 1, GIT_OBJ_BLOB); + + /** + * Now that we've written the object, we can check out what SHA1 was + * generated when the object was written to our database. + */ + git_oid_fmt(oid_hex, oid); + printf("Written Object: %s\n", oid_hex); +} + +/** + * #### Writing Commits + * + * libgit2 provides a couple of methods to create commit objects easily as + * well. There are four different create signatures, we'll just show one + * of them here. You can read about the other ones in the [commit API + * docs][cd]. + * + * [cd]: http://libgit2.github.com/libgit2/#HEAD/group/commit + */ +static void commit_writing(git_repository *repo) +{ + git_oid tree_id, parent_id, commit_id; + git_tree *tree; + git_commit *parent; + const git_signature *author, *cmtter; + char oid_hex[GIT_OID_HEXSZ+1] = { 0 }; + + printf("\n*Commit Writing*\n"); + + /** + * Creating signatures for an authoring identity and time is simple. You + * will need to do this to specify who created a commit and when. Default + * values for the name and email should be found in the `user.name` and + * `user.email` configuration options. See the `config` section of this + * example file to see how to access config values. + */ + git_signature_new((git_signature **)&author, + "Scott Chacon", "schacon@gmail.com", 123456789, 60); + git_signature_new((git_signature **)&cmtter, + "Scott A Chacon", "scott@github.com", 987654321, 90); + + /** + * Commit objects need a tree to point to and optionally one or more + * parents. Here we're creating oid objects to create the commit with, + * but you can also use + */ + git_oid_fromstr(&tree_id, "f60079018b664e4e79329a7ef9559c8d9e0378d1"); + git_tree_lookup(&tree, repo, &tree_id); + git_oid_fromstr(&parent_id, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + git_commit_lookup(&parent, repo, &parent_id); + + /** + * Here we actually create the commit object with a single call with all + * the values we need to create the commit. The SHA key is written to the + * `commit_id` variable here. + */ + git_commit_create_v( + &commit_id, /* out id */ + repo, + NULL, /* do not update the HEAD */ + author, + cmtter, + NULL, /* use default message encoding */ + "example commit", + tree, + 1, parent); + + /** + * Now we can take a look at the commit SHA we've generated. + */ + git_oid_fmt(oid_hex, &commit_id); + printf("New Commit: %s\n", oid_hex); +} + +/** + * ### Object Parsing + * + * libgit2 has methods to parse every object type in Git so you don't have + * to work directly with the raw data. This is much faster and simpler + * than trying to deal with the raw data yourself. + */ + +/** + * #### Commit Parsing + * + * [Parsing commit objects][pco] is simple and gives you access to all the + * data in the commit - the author (name, email, datetime), committer + * (same), tree, message, encoding and parent(s). + * + * [pco]: http://libgit2.github.com/libgit2/#HEAD/group/commit + */ +static void commit_parsing(git_repository *repo) +{ + const git_signature *author, *cmtter; + git_commit *commit, *parent; + git_oid oid; + char oid_hex[GIT_OID_HEXSZ+1]; + const char *message; + unsigned int parents, p; + int error; + time_t time; + + printf("\n*Commit Parsing*\n"); + + git_oid_fromstr(&oid, "8496071c1b46c854b31185ea97743be6a8774479"); + + error = git_commit_lookup(&commit, repo, &oid); + check_error(error, "looking up commit"); + + /** + * Each of the properties of the commit object are accessible via methods, + * including commonly needed variations, such as `git_commit_time` which + * returns the author time and `git_commit_message` which gives you the + * commit message (as a NUL-terminated string). + */ + message = git_commit_message(commit); + author = git_commit_author(commit); + cmtter = git_commit_committer(commit); + time = git_commit_time(commit); + + /** + * The author and committer methods return [git_signature] structures, + * which give you name, email and `when`, which is a `git_time` structure, + * giving you a timestamp and timezone offset. + */ + printf("Author: %s (%s)\nCommitter: %s (%s)\nDate: %s\nMessage: %s\n", + author->name, author->email, + cmtter->name, cmtter->email, + ctime(&time), message); + + /** + * Commits can have zero or more parents. The first (root) commit will + * have no parents, most commits will have one (i.e. the commit it was + * based on) and merge commits will have two or more. Commits can + * technically have any number, though it's rare to have more than two. + */ + parents = git_commit_parentcount(commit); + for (p = 0;p < parents;p++) { + memset(oid_hex, 0, sizeof(oid_hex)); + + git_commit_parent(&parent, commit, p); + git_oid_fmt(oid_hex, git_commit_id(parent)); + printf("Parent: %s\n", oid_hex); + git_commit_free(parent); + } + + git_commit_free(commit); +} + +/** + * #### Tag Parsing + * + * You can parse and create tags with the [tag management API][tm], which + * functions very similarly to the commit lookup, parsing and creation + * methods, since the objects themselves are very similar. + * + * [tm]: http://libgit2.github.com/libgit2/#HEAD/group/tag + */ +static void tag_parsing(git_repository *repo) +{ + git_commit *commit; + git_otype type; + git_tag *tag; + git_oid oid; + const char *name, *message; + int error; + + printf("\n*Tag Parsing*\n"); + + /** + * We create an oid for the tag object if we know the SHA and look it up + * the same way that we would a commit (or any other object). + */ + git_oid_fromstr(&oid, "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"); + + error = git_tag_lookup(&tag, repo, &oid); + check_error(error, "looking up tag"); + + /** + * Now that we have the tag object, we can extract the information it + * generally contains: the target (usually a commit object), the type of + * the target object (usually 'commit'), the name ('v1.0'), the tagger (a + * git_signature - name, email, timestamp), and the tag message. + */ + git_tag_target((git_object **)&commit, tag); + name = git_tag_name(tag); /* "test" */ + type = git_tag_target_type(tag); /* GIT_OBJ_COMMIT (otype enum) */ + message = git_tag_message(tag); /* "tag message\n" */ + printf("Tag Name: %s\nTag Type: %s\nTag Message: %s\n", + name, git_object_type2string(type), message); + + git_commit_free(commit); +} + +/** + * #### Tree Parsing + * + * [Tree parsing][tp] is a bit different than the other objects, in that + * we have a subtype which is the tree entry. This is not an actual + * object type in Git, but a useful structure for parsing and traversing + * tree entries. + * + * [tp]: http://libgit2.github.com/libgit2/#HEAD/group/tree + */ +static void tree_parsing(git_repository *repo) +{ + const git_tree_entry *entry; + size_t cnt; + git_object *obj; + git_tree *tree; + git_oid oid; + + printf("\n*Tree Parsing*\n"); + + /** + * Create the oid and lookup the tree object just like the other objects. + */ + git_oid_fromstr(&oid, "f60079018b664e4e79329a7ef9559c8d9e0378d1"); + git_tree_lookup(&tree, repo, &oid); + + /** + * Getting the count of entries in the tree so you can iterate over them + * if you want to. + */ + cnt = git_tree_entrycount(tree); /* 2 */ + printf("tree entries: %d\n", (int) cnt); + + entry = git_tree_entry_byindex(tree, 0); + printf("Entry name: %s\n", git_tree_entry_name(entry)); /* "README" */ + + /** + * You can also access tree entries by name if you know the name of the + * entry you're looking for. + */ + entry = git_tree_entry_byname(tree, "README"); + git_tree_entry_name(entry); /* "README" */ + + /** + * Once you have the entry object, you can access the content or subtree + * (or commit, in the case of submodules) that it points to. You can also + * get the mode if you want. + */ + git_tree_entry_to_object(&obj, repo, entry); /* blob */ + + /** + * Remember to close the looked-up object once you are done using it + */ + git_object_free(obj); +} + +/** + * #### Blob Parsing + * + * The last object type is the simplest and requires the least parsing + * help. Blobs are just file contents and can contain anything, there is + * no structure to it. The main advantage to using the [simple blob + * api][ba] is that when you're creating blobs you don't have to calculate + * the size of the content. There is also a helper for reading a file + * from disk and writing it to the db and getting the oid back so you + * don't have to do all those steps yourself. + * + * [ba]: http://libgit2.github.com/libgit2/#HEAD/group/blob + */ +static void blob_parsing(git_repository *repo) +{ + git_blob *blob; + git_oid oid; + + printf("\n*Blob Parsing*\n"); + + git_oid_fromstr(&oid, "1385f264afb75a56a5bec74243be9b367ba4ca08"); + git_blob_lookup(&blob, repo, &oid); + + /** + * You can access a buffer with the raw contents of the blob directly. + * Note that this buffer may not be contain ASCII data for certain blobs + * (e.g. binary files): do not consider the buffer a NULL-terminated + * string, and use the `git_blob_rawsize` attribute to find out its exact + * size in bytes + * */ + printf("Blob Size: %ld\n", (long)git_blob_rawsize(blob)); /* 8 */ + git_blob_rawcontent(blob); /* "content" */ +} + +/** + * ### Revwalking + * + * The libgit2 [revision walking api][rw] provides methods to traverse the + * directed graph created by the parent pointers of the commit objects. + * Since all commits point back to the commit that came directly before + * them, you can walk this parentage as a graph and find all the commits + * that were ancestors of (reachable from) a given starting point. This + * can allow you to create `git log` type functionality. + * + * [rw]: http://libgit2.github.com/libgit2/#HEAD/group/revwalk + */ +static void revwalking(git_repository *repo) +{ + const git_signature *cauth; + const char *cmsg; + int error; + git_revwalk *walk; + git_commit *wcommit; + git_oid oid; + + printf("\n*Revwalking*\n"); + + git_oid_fromstr(&oid, "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + + /** + * To use the revwalker, create a new walker, tell it how you want to sort + * the output and then push one or more starting points onto the walker. + * If you want to emulate the output of `git log` you would push the SHA + * of the commit that HEAD points to into the walker and then start + * traversing them. You can also 'hide' commits that you want to stop at + * or not see any of their ancestors. So if you want to emulate `git log + * branch1..branch2`, you would push the oid of `branch2` and hide the oid + * of `branch1`. + */ + git_revwalk_new(&walk, repo); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL | GIT_SORT_REVERSE); + git_revwalk_push(walk, &oid); + + /** + * Now that we have the starting point pushed onto the walker, we start + * asking for ancestors. It will return them in the sorting order we asked + * for as commit oids. We can then lookup and parse the committed pointed + * at by the returned OID; note that this operation is specially fast + * since the raw contents of the commit object will be cached in memory + */ + while ((git_revwalk_next(&oid, walk)) == 0) { + error = git_commit_lookup(&wcommit, repo, &oid); + check_error(error, "looking up commit during revwalk"); + + cmsg = git_commit_message(wcommit); + cauth = git_commit_author(wcommit); + printf("%s (%s)\n", cmsg, cauth->email); + + git_commit_free(wcommit); + } + + /** + * Like the other objects, be sure to free the revwalker when you're done + * to prevent memory leaks. Also, make sure that the repository being + * walked it not deallocated while the walk is in progress, or it will + * result in undefined behavior + */ + git_revwalk_free(walk); +} + +/** + * ### Index File Manipulation * + * The [index file API][gi] allows you to read, traverse, update and write + * the Git index file (sometimes thought of as the staging area). + * + * [gi]: http://libgit2.github.com/libgit2/#HEAD/group/index + */ +static void index_walking(git_repository *repo) +{ + git_index *index; + unsigned int i, ecount; + + printf("\n*Index Walking*\n"); + + /** + * You can either open the index from the standard location in an open + * repository, as we're doing here, or you can open and manipulate any + * index file with `git_index_open_bare()`. The index for the repository + * will be located and loaded from disk. + */ + git_repository_index(&index, repo); + + /** + * For each entry in the index, you can get a bunch of information + * including the SHA (oid), path and mode which map to the tree objects + * that are written out. It also has filesystem properties to help + * determine what to inspect for changes (ctime, mtime, dev, ino, uid, + * gid, file_size and flags) All these properties are exported publicly in + * the `git_index_entry` struct + */ + ecount = git_index_entrycount(index); + for (i = 0; i < ecount; ++i) { + const git_index_entry *e = git_index_get_byindex(index, i); + + printf("path: %s\n", e->path); + printf("mtime: %d\n", (int)e->mtime.seconds); + printf("fs: %d\n", (int)e->file_size); + } + + git_index_free(index); +} + +/** + * ### References + * + * The [reference API][ref] allows you to list, resolve, create and update + * references such as branches, tags and remote references (everything in + * the .git/refs directory). + * + * [ref]: http://libgit2.github.com/libgit2/#HEAD/group/reference + */ +static void reference_listing(git_repository *repo) +{ + git_strarray ref_list; + const char *refname; + git_reference *ref; + unsigned i; + char oid_hex[GIT_OID_HEXSZ+1]; + + printf("\n*Reference Listing*\n"); + + /** + * Here we will implement something like `git for-each-ref` simply listing + * out all available references and the object SHA they resolve to. + * + * Now that we have the list of reference names, we can lookup each ref + * one at a time and resolve them to the SHA, then print both values out. + */ + + git_reference_list(&ref_list, repo); + + for (i = 0; i < ref_list.count; ++i) { + memset(oid_hex, 0, sizeof(oid_hex)); + refname = ref_list.strings[i]; + git_reference_lookup(&ref, repo, refname); + + switch (git_reference_type(ref)) { + case GIT_REF_OID: + git_oid_fmt(oid_hex, git_reference_target(ref)); + printf("%s [%s]\n", refname, oid_hex); + break; + + case GIT_REF_SYMBOLIC: + printf("%s => %s\n", refname, git_reference_symbolic_target(ref)); + break; + default: + fprintf(stderr, "Unexpected reference type\n"); + exit(1); + } + } + + git_strarray_free(&ref_list); +} + +/** + * ### Config Files + * + * The [config API][config] allows you to list and updatee config values + * in any of the accessible config file locations (system, global, local). + * + * [config]: http://libgit2.github.com/libgit2/#HEAD/group/config + */ +static void config_files(const char *repo_path) +{ + const char *email; + char config_path[256]; + int32_t j; + git_config *cfg; + + printf("\n*Config Listing*\n"); + + /** + * Open a config object so we can read global values from it. + */ + sprintf(config_path, "%s/config", repo_path); + check_error(git_config_open_ondisk(&cfg, config_path), "opening config"); + + git_config_get_int32(&j, cfg, "help.autocorrect"); + printf("Autocorrect: %d\n", j); + + git_config_get_string(&email, cfg, "user.email"); + printf("Email: %s\n", email); +} diff --git a/examples/network/fetch.c b/examples/network/fetch.c index 177359b88..10974a9f1 100644 --- a/examples/network/fetch.c +++ b/examples/network/fetch.c @@ -55,6 +55,8 @@ static int update_cb(const char *refname, const git_oid *a, const git_oid *b, vo */ static int transfer_progress_cb(const git_transfer_progress *stats, void *payload) { + (void)payload; + if (stats->received_objects == stats->total_objects) { printf("Resolving deltas %d/%d\r", stats->indexed_deltas, stats->total_deltas); @@ -71,7 +73,6 @@ int fetch(git_repository *repo, int argc, char **argv) { git_remote *remote = NULL; const git_transfer_progress *stats; - struct dl_data data; git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT; if (argc < 2) { @@ -79,14 +80,13 @@ int fetch(git_repository *repo, int argc, char **argv) return EXIT_FAILURE; } - // Figure out whether it's a named remote or a URL + /* Figure out whether it's a named remote or a URL */ printf("Fetching %s for repo %p\n", argv[1], repo); - if (git_remote_lookup(&remote, repo, argv[1]) < 0) { + if (git_remote_lookup(&remote, repo, argv[1]) < 0) if (git_remote_create_anonymous(&remote, repo, argv[1]) < 0) - return -1; - } + goto on_error; - // Set up the callbacks (only update_tips for now) + /* Set up the callbacks (only update_tips for now) */ fetch_opts.callbacks.update_tips = &update_cb; fetch_opts.callbacks.sideband_progress = &progress_cb; fetch_opts.callbacks.transfer_progress = transfer_progress_cb; @@ -98,7 +98,7 @@ int fetch(git_repository *repo, int argc, char **argv) * "fetch". */ if (git_remote_fetch(remote, NULL, &fetch_opts, "fetch") < 0) - return -1; + goto on_error; /** * If there are local objects (we got a thin pack), then tell diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index c9da79f5f..9329da5d9 100644 --- a/examples/network/ls-remote.c +++ b/examples/network/ls-remote.c @@ -26,7 +26,7 @@ static int use_remote(git_repository *repo, char *name) */ callbacks.credentials = cred_acquire_cb; - error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL); + error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL, NULL); if (error < 0) goto cleanup; diff --git a/include/git2.h b/include/git2.h index ac4a63160..bc4333cc9 100644 --- a/include/git2.h +++ b/include/git2.h @@ -40,6 +40,7 @@ #include "git2/pack.h" #include "git2/patch.h" #include "git2/pathspec.h" +#include "git2/proxy.h" #include "git2/rebase.h" #include "git2/refdb.h" #include "git2/reflog.h" diff --git a/include/git2/blob.h b/include/git2/blob.h index 9a57c37f5..70204c440 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -150,46 +150,48 @@ GIT_EXTERN(int) git_blob_create_fromworkdir(git_oid *id, git_repository *repo, c */ GIT_EXTERN(int) git_blob_create_fromdisk(git_oid *id, git_repository *repo, const char *path); - -typedef int (*git_blob_chunk_cb)(char *content, size_t max_length, void *payload); - /** - * Write a loose blob to the Object Database from a - * provider of chunks of data. + * Create a stream to write a new blob into the object db + * + * This function may need to buffer the data on disk and will in + * general not be the right choice if you know the size of the data + * to write. If you have data in memory, use + * `git_blob_create_frombuffer()`. If you do not, but know the size of + * the contents (and don't want/need to perform filtering), use + * `git_odb_open_wstream()`. + * + * Don't close this stream yourself but pass it to + * `git_blob_create_fromstream_commit()` to commit the write to the + * object db and get the object id. * * If the `hintpath` parameter is filled, it will be used to determine * what git filters should be applied to the object before it is written * to the object database. * - * The implementation of the callback MUST respect the following rules: - * - * - `content` must be filled by the callback. The maximum number of - * bytes that the buffer can accept per call is defined by the - * `max_length` parameter. Allocation and freeing of the buffer will - * be taken care of by libgit2. - * - * - The `callback` must return the number of bytes that have been - * written to the `content` buffer. - * - * - When there is no more data to stream, `callback` should return 0. - * This will prevent it from being invoked anymore. - * - * - If an error occurs, the callback should return a negative value. - * This value will be returned to the caller. - * - * @param id Return the id of the written blob + * @param out the stream into which to write * @param repo Repository where the blob will be written. * This repository can be bare or not. * @param hintpath If not NULL, will be used to select data filters * to apply onto the content of the blob to be created. - * @return 0 or error code (from either libgit2 or callback function) + * @return 0 or error code */ -GIT_EXTERN(int) git_blob_create_fromchunks( - git_oid *id, +GIT_EXTERN(int) git_blob_create_fromstream( + git_writestream **out, git_repository *repo, - const char *hintpath, - git_blob_chunk_cb callback, - void *payload); + const char *hintpath); + +/** + * Close the stream and write the blob to the object db + * + * The stream will be closed and freed. + * + * @param out the id of the new blob + * @param stream the stream to close + * @return 0 or an error code + */ +GIT_EXTERN(int) git_blob_create_fromstream_commit( + git_oid *out, + git_writestream *stream); /** * Write an in-memory buffer to the ODB as a blob @@ -216,6 +218,15 @@ GIT_EXTERN(int) git_blob_create_frombuffer( */ GIT_EXTERN(int) git_blob_is_binary(const git_blob *blob); +/** + * Create an in-memory copy of a blob. The copy must be explicitly + * free'd or it will leak. + * + * @param out Pointer to store the copy of the object + * @param source Original object to copy + */ +GIT_EXTERN(int) git_blob_dup(git_blob **out, git_blob *source); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/commit.h b/include/git2/commit.h index 3488c7440..4cc637466 100644 --- a/include/git2/commit.h +++ b/include/git2/commit.h @@ -394,6 +394,82 @@ GIT_EXTERN(int) git_commit_amend( const char *message, const git_tree *tree); +/** + * Create a commit and write it into a buffer + * + * Create a commit as with `git_commit_create()` but instead of + * writing it to the objectdb, write the contents of the object into a + * buffer. + * + * @param out the buffer into which to write the commit object content + * + * @param repo Repository where the referenced tree and parents live + * + * @param author Signature with author and author time of commit + * + * @param committer Signature with committer and * commit time of commit + * + * @param message_encoding The encoding for the message in the + * commit, represented with a standard encoding name. + * E.g. "UTF-8". If NULL, no encoding header is written and + * UTF-8 is assumed. + * + * @param message Full message for this commit + * + * @param tree An instance of a `git_tree` object that will + * be used as the tree for the commit. This tree object must + * also be owned by the given `repo`. + * + * @param parent_count Number of parents for this commit + * + * @param parents Array of `parent_count` pointers to `git_commit` + * objects that will be used as the parents for this commit. This + * array may be NULL if `parent_count` is 0 (root commit). All the + * given commits must be owned by the `repo`. + * + * @return 0 or an error code + */ +GIT_EXTERN(int) git_commit_create_buffer( + git_buf *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]); + +/** + * Create a commit object from the given buffer and signature + * + * Given the unsigned commit object's contents, its signature and the + * header field in which to store the signature, attach the signature + * to the commit and write it into the given repository. + * + * @param out the resulting commit id + * @param commit_content the content of the unsigned commit object + * @param signature the signature to add to the commit + * @param signature_field which header field should contain this + * signature. Leave `NULL` for the default of "gpgsig" + * @return 0 or an error code + */ +GIT_EXTERN(int) git_commit_create_with_signature( + git_oid *out, + git_repository *repo, + const char *commit_content, + const char *signature, + const char *signature_field); + +/** + * Create an in-memory copy of a commit. The copy must be explicitly + * free'd or it will leak. + * + * @param out Pointer to store the copy of the commit + * @param source Original commit to copy + */ +GIT_EXTERN(int) git_commit_dup(git_commit **out, git_commit *source); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/common.h b/include/git2/common.h index d7428d811..99c99812b 100644 --- a/include/git2/common.h +++ b/include/git2/common.h @@ -109,9 +109,27 @@ GIT_EXTERN(void) git_libgit2_version(int *major, int *minor, int *rev); * was compiled */ typedef enum { + /** + * If set, libgit2 was built thread-aware and can be safely used from multiple + * threads. + */ GIT_FEATURE_THREADS = (1 << 0), + /** + * If set, libgit2 was built with and linked against a TLS implementation. + * Custom TLS streams may still be added by the user to support HTTPS + * regardless of this. + */ GIT_FEATURE_HTTPS = (1 << 1), + /** + * If set, libgit2 was built with and linked against libssh2. A custom + * transport may still be added by the user to support libssh2 regardless of + * this. + */ GIT_FEATURE_SSH = (1 << 2), + /** + * If set, libgit2 was built with support for sub-second resolution in file + * modification times. + */ GIT_FEATURE_NSEC = (1 << 3), } git_feature_t; @@ -158,6 +176,7 @@ typedef enum { GIT_OPT_SET_USER_AGENT, GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, GIT_OPT_SET_SSL_CIPHERS, + GIT_OPT_GET_USER_AGENT, } git_libgit2_opt_t; /** @@ -268,7 +287,8 @@ typedef enum { * > to ensure that all inputs to the new objects are valid. For * > example, when this is enabled, the parent(s) and tree inputs * > will be validated when creating a new commit. This defaults - * > to disabled. + * > to enabled. + * * * opts(GIT_OPT_SET_SSL_CIPHERS, const char *ciphers) * * > Set the SSL ciphers use for HTTPS connections. diff --git a/include/git2/describe.h b/include/git2/describe.h index 3044d9165..971eb350c 100644 --- a/include/git2/describe.h +++ b/include/git2/describe.h @@ -44,8 +44,8 @@ typedef enum { typedef struct git_describe_options { unsigned int version; - unsigned int max_candidates_tags; /** default: 10 */ - unsigned int describe_strategy; /** default: GIT_DESCRIBE_DEFAULT */ + unsigned int max_candidates_tags; /**< default: 10 */ + unsigned int describe_strategy; /**< default: GIT_DESCRIBE_DEFAULT */ const char *pattern; /** * When calculating the distance from the matching tag or @@ -105,6 +105,9 @@ typedef struct { GIT_EXTERN(int) git_describe_init_format_options(git_describe_format_options *opts, unsigned int version); +/** + * A struct that stores the result of a describe operation. + */ typedef struct git_describe_result git_describe_result; /** diff --git a/include/git2/diff.h b/include/git2/diff.h index c35701a46..4f0871dab 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -264,7 +264,7 @@ typedef enum { * link, a submodule commit id, or even a tree (although that only if you * are tracking type changes or ignored/untracked directories). * - * The `oid` is the `git_oid` of the item. If the entry represents an + * The `id` is the `git_oid` of the item. If the entry represents an * absent side of a diff (e.g. the `old_file` of a `GIT_DELTA_ADDED` delta), * then the oid will be zeroes. * @@ -277,6 +277,11 @@ typedef enum { * * `mode` is, roughly, the stat() `st_mode` value for the item. This will * be restricted to one of the `git_filemode_t` values. + * + * The `id_abbrev` represents the known length of the `id` field, when + * converted to a hex string. It is generally `GIT_OID_HEXSZ`, unless this + * delta was created from reading a patch file, in which case it may be + * abbreviated to something reasonable, like 7 characters. */ typedef struct { git_oid id; @@ -284,6 +289,7 @@ typedef struct { git_off_t size; uint32_t flags; uint16_t mode; + uint16_t id_abbrev; } git_diff_file; /** @@ -448,6 +454,8 @@ typedef int (*git_diff_file_cb)( float progress, void *payload); +#define GIT_DIFF_HUNK_HEADER_SIZE 128 + /** * When producing a binary diff, the binary data returned will be * either the deflated full ("literal") contents of the file, or @@ -482,6 +490,14 @@ typedef struct { /** Structure describing the binary contents of a diff. */ typedef struct { + /** + * Whether there is data in this binary structure or not. If this + * is `1`, then this was produced and included binary content. If + * this is `0` then this was generated knowing only that a binary + * file changed but without providing the data, probably from a patch + * that said `Binary files a/file.txt and b/file.txt differ`. + */ + unsigned int contains_data; git_diff_binary_file old_file; /**< The contents of the old file. */ git_diff_binary_file new_file; /**< The contents of the new file. */ } git_diff_binary; @@ -499,12 +515,12 @@ typedef int(*git_diff_binary_cb)( * Structure describing a hunk of a diff. */ typedef struct { - int old_start; /**< Starting line number in old_file */ - int old_lines; /**< Number of lines in old_file */ - int new_start; /**< Starting line number in new_file */ - int new_lines; /**< Number of lines in new_file */ - size_t header_len; /**< Number of bytes in header text */ - char header[128]; /**< Header text, NUL-byte terminated */ + int old_start; /** Starting line number in old_file */ + int old_lines; /** Number of lines in old_file */ + int new_start; /** Starting line number in new_file */ + int new_lines; /** Number of lines in new_file */ + size_t header_len; /** Number of bytes in header text */ + char header[GIT_DIFF_HUNK_HEADER_SIZE]; /** Header text, NUL-byte terminated */ } git_diff_hunk; /** @@ -1046,6 +1062,21 @@ GIT_EXTERN(int) git_diff_print( git_diff_line_cb print_cb, void *payload); +/** + * Produce the complete formatted text output from a diff into a + * buffer. + * + * @param out A pointer to a user-allocated git_buf that will + * contain the diff text + * @param diff A git_diff generated by one of the above functions. + * @param format A git_diff_format_t value to pick the text format. + * @return 0 on success or error code + */ +GIT_EXTERN(int) git_diff_to_buf( + git_buf *out, + git_diff *diff, + git_diff_format_t format); + /**@}*/ @@ -1166,6 +1197,30 @@ GIT_EXTERN(int) git_diff_buffers( git_diff_line_cb line_cb, void *payload); +/** + * Read the contents of a git patch file into a `git_diff` object. + * + * The diff object produced is similar to the one that would be + * produced if you actually produced it computationally by comparing + * two trees, however there may be subtle differences. For example, + * a patch file likely contains abbreviated object IDs, so the + * object IDs in a `git_diff_delta` produced by this function will + * also be abbreviated. + * + * This function will only read patch files created by a git + * implementation, it will not read unified diffs produced by + * the `diff` program, nor any other types of patch files. + * + * @param out A pointer to a git_diff pointer that will be allocated. + * @param content The contents of a patch file + * @param content_len The length of the patch file contents + * @return 0 or an error code + */ +GIT_EXTERN(int) git_diff_from_buffer( + git_diff **out, + const char *content, + size_t content_len); + /** * This is an opaque structure which is allocated by `git_diff_get_stats`. * You are responsible for releasing the object memory when done, using the diff --git a/include/git2/errors.h b/include/git2/errors.h index 3ecea34bf..e959ffd8a 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -98,7 +98,8 @@ typedef enum { GITERR_CHERRYPICK, GITERR_DESCRIBE, GITERR_REBASE, - GITERR_FILESYSTEM + GITERR_FILESYSTEM, + GITERR_PATCH, } git_error_t; /** diff --git a/include/git2/index.h b/include/git2/index.h index 466765be3..e58b3287e 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -251,6 +251,31 @@ GIT_EXTERN(int) git_index_caps(const git_index *index); */ GIT_EXTERN(int) git_index_set_caps(git_index *index, int caps); +/** + * Get index on-disk version. + * + * Valid return values are 2, 3, or 4. If 3 is returned, an index + * with version 2 may be written instead, if the extension data in + * version 3 is not necessary. + * + * @param index An existing index object + * @return the index version + */ +GIT_EXTERN(unsigned int) git_index_version(git_index *index); + +/** + * Set index on-disk version. + * + * Valid values are 2, 3, or 4. If 2 is given, git_index_write may + * write an index with version 3 instead, if necessary to accurately + * represent the index. + * + * @param index An existing index object + * @param version The new version number + * @return 0 on success, -1 on failure + */ +GIT_EXTERN(int) git_index_set_version(git_index *index, unsigned int version); + /** * Update the contents of an existing index object in memory by reading * from the hard disk. diff --git a/include/git2/merge.h b/include/git2/merge.h index 560797a0c..c6f6cba6c 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -273,7 +273,16 @@ typedef struct { */ unsigned int recursion_limit; - /** Flags for handling conflicting content. */ + /** + * Default merge driver to be used when both sides of a merge have + * changed. The default is the `text` driver. + */ + const char *default_driver; + + /** + * Flags for handling conflicting content, to be used with the standard + * (`text`) merge driver. + */ git_merge_file_favor_t file_favor; /** see `git_merge_file_flag_t` above */ diff --git a/include/git2/odb.h b/include/git2/odb.h index 4f1e18bc1..b3ed2706c 100644 --- a/include/git2/odb.h +++ b/include/git2/odb.h @@ -10,6 +10,7 @@ #include "common.h" #include "types.h" #include "oid.h" +#include "oidarray.h" /** * @file git2/odb.h @@ -159,7 +160,8 @@ GIT_EXTERN(int) git_odb_read_header(size_t *len_out, git_otype *type_out, git_od GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id); /** - * Determine if objects can be found in the object database from a short OID. + * Determine if an object can be found in the object database by an + * abbreviated object ID. * * @param out The full OID of the found object if just one is found. * @param db The database to be searched for the given object. @@ -171,6 +173,50 @@ GIT_EXTERN(int) git_odb_exists(git_odb *db, const git_oid *id); GIT_EXTERN(int) git_odb_exists_prefix( git_oid *out, git_odb *db, const git_oid *short_id, size_t len); +/** + * The information about object IDs to query in `git_odb_expand_ids`, + * which will be populated upon return. + */ +typedef struct git_odb_expand_id { + /** The object ID to expand */ + git_oid id; + + /** + * The length of the object ID (in nibbles, or packets of 4 bits; the + * number of hex characters) + * */ + unsigned short length; + + /** + * The (optional) type of the object to search for; leave as `0` or set + * to `GIT_OBJ_ANY` to query for any object matching the ID. + */ + git_otype type; +} git_odb_expand_id; + +/** + * Determine if one or more objects can be found in the object database + * by their abbreviated object ID and type. The given array will be + * updated in place: for each abbreviated ID that is unique in the + * database, and of the given type (if specified), the full object ID, + * object ID length (`GIT_OID_HEXSZ`) and type will be written back to + * the array. For IDs that are not found (or are ambiguous), the + * array entry will be zeroed. + * + * Note that since this function operates on multiple objects, the + * underlying database will not be asked to be reloaded if an object is + * not found (which is unlike other object database operations.) + * + * @param db The database to be searched for the given objects. + * @param ids An array of short object IDs to search for + * @param count The length of the `ids` array + * @return 0 on success or an error code on failure + */ +GIT_EXTERN(int) git_odb_expand_ids( + git_odb *db, + git_odb_expand_id *ids, + size_t count); + /** * Refresh the object database to load newly added files. * diff --git a/include/git2/pack.h b/include/git2/pack.h index 4941998eb..2dfd825e9 100644 --- a/include/git2/pack.h +++ b/include/git2/pack.h @@ -196,7 +196,7 @@ GIT_EXTERN(int) git_packbuilder_foreach(git_packbuilder *pb, git_packbuilder_for * @param pb the packbuilder * @return the number of objects in the packfile */ -GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); +GIT_EXTERN(size_t) git_packbuilder_object_count(git_packbuilder *pb); /** * Get the number of objects the packbuilder has already written out @@ -204,13 +204,13 @@ GIT_EXTERN(uint32_t) git_packbuilder_object_count(git_packbuilder *pb); * @param pb the packbuilder * @return the number of objects which have already been written */ -GIT_EXTERN(uint32_t) git_packbuilder_written(git_packbuilder *pb); +GIT_EXTERN(size_t) git_packbuilder_written(git_packbuilder *pb); /** Packbuilder progress notification function */ typedef int (*git_packbuilder_progress)( int stage, - unsigned int current, - unsigned int total, + uint32_t current, + uint32_t total, void *payload); /** diff --git a/include/git2/patch.h b/include/git2/patch.h index 790cb74fc..4eb9f0263 100644 --- a/include/git2/patch.h +++ b/include/git2/patch.h @@ -191,7 +191,7 @@ GIT_EXTERN(int) git_patch_get_hunk( * * @param patch The git_patch object * @param hunk_idx Index of the hunk - * @return Number of lines in hunk or -1 if invalid hunk index + * @return Number of lines in hunk or GIT_ENOTFOUND if invalid hunk index */ GIT_EXTERN(int) git_patch_num_lines_in_hunk( const git_patch *patch, diff --git a/include/git2/proxy.h b/include/git2/proxy.h new file mode 100644 index 000000000..194cbb651 --- /dev/null +++ b/include/git2/proxy.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_proxy_h__ +#define INCLUDE_git_proxy_h__ + +#include "common.h" +#include "transport.h" + +GIT_BEGIN_DECL + +/** + * The type of proxy to use. + */ +typedef enum { + /** + * Do not attempt to connect through a proxy + * + * If built against libcurl, it itself may attempt to connect + * to a proxy if the environment variables specify it. + */ + GIT_PROXY_NONE, + /** + * Try to auto-detect the proxy from the git configuration. + */ + GIT_PROXY_AUTO, + /** + * Connect via the URL given in the options + */ + GIT_PROXY_SPECIFIED, +} git_proxy_t; + +/** + * Options for connecting through a proxy + * + * Note that not all types may be supported, depending on the platform + * and compilation options. + */ +typedef struct { + unsigned int version; + + /** + * The type of proxy to use, by URL, auto-detect. + */ + git_proxy_t type; + + /** + * The URL of the proxy. + */ + const char *url; + + /** + * This will be called if the remote host requires + * authentication in order to connect to it. + * + * Returning GIT_PASSTHROUGH will make libgit2 behave as + * though this field isn't set. + */ + git_cred_acquire_cb credentials; + + /** + * If cert verification fails, this will be called to let the + * user make the final decision of whether to allow the + * connection to proceed. Returns 1 to allow the connection, 0 + * to disallow it or a negative value to indicate an error. + */ + git_transport_certificate_check_cb certificate_check; + + /** + * Payload to be provided to the credentials and certificate + * check callbacks. + */ + void *payload; +} git_proxy_options; + +#define GIT_PROXY_OPTIONS_VERSION 1 +#define GIT_PROXY_OPTIONS_INIT {GIT_PROXY_OPTIONS_VERSION} + +/** + * Initialize a proxy options structure + * + * @param opts the options struct to initialize + * @param version the version of the struct, use `GIT_PROXY_OPTIONS_VERSION` + */ +GIT_EXTERN(int) git_proxy_init_options(git_proxy_options *opts, unsigned int version); + +GIT_END_DECL + +#endif diff --git a/include/git2/refs.h b/include/git2/refs.h index db84ed03a..dee28cb5b 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -461,6 +461,17 @@ GIT_EXTERN(int) git_reference_foreach_name( git_reference_foreach_name_cb callback, void *payload); +/** + * Create a copy of an existing reference. + * + * Call `git_reference_free` to free the data. + * + * @param dest pointer where to store the copy + * @param source object to copy + * @return 0 or an error code + */ +GIT_EXTERN(int) git_reference_dup(git_reference **dest, git_reference *source); + /** * Free the given reference. * diff --git a/include/git2/remote.h b/include/git2/remote.h index c42d96710..691114172 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -15,6 +15,7 @@ #include "strarray.h" #include "transport.h" #include "pack.h" +#include "proxy.h" /** * @file git2/remote.h @@ -25,8 +26,6 @@ */ GIT_BEGIN_DECL -typedef int (*git_remote_rename_problem_cb)(const char *problematic_refspec, void *payload); - /** * Add a remote with the default fetch refspec to the repository's configuration. * @@ -241,10 +240,11 @@ GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, * @param direction GIT_DIRECTION_FETCH if you want to fetch or * GIT_DIRECTION_PUSH if you want to push * @param callbacks the callbacks to use for this connection + * @param proxy_opts proxy settings * @param custom_headers extra HTTP headers to use in this connection * @return 0 or an error code */ -GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_strarray *custom_headers); +GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy_opts, const git_strarray *custom_headers); /** * Get the remote repository's reference advertisement list @@ -358,6 +358,8 @@ typedef struct { } git_push_update; /** + * Callback used to inform of upcoming updates. + * * @param updates an array containing the updates which will be sent * as commands to the destination. * @param len number of elements in `updates` @@ -376,7 +378,7 @@ struct git_remote_callbacks { /** * Textual progress from the remote. Text send over the * progress side-band will be passed to this function (this is - * the 'counting objects' output. + * the 'counting objects' output). */ git_transport_message_cb sideband_progress; @@ -401,7 +403,7 @@ struct git_remote_callbacks { * connection to proceed. Returns 1 to allow the connection, 0 * to disallow it or a negative value to indicate an error. */ - git_transport_certificate_check_cb certificate_check; + git_transport_certificate_check_cb certificate_check; /** * During the download of new data, this will be regularly @@ -548,6 +550,11 @@ typedef struct { */ git_remote_autotag_option_t download_tags; + /** + * Proxy options to use, by default no proxy is used. + */ + git_proxy_options proxy_opts; + /** * Extra headers for this fetch operation */ @@ -555,13 +562,14 @@ typedef struct { } git_fetch_options; #define GIT_FETCH_OPTIONS_VERSION 1 -#define GIT_FETCH_OPTIONS_INIT { GIT_FETCH_OPTIONS_VERSION, GIT_REMOTE_CALLBACKS_INIT, GIT_FETCH_PRUNE_UNSPECIFIED, 1 } +#define GIT_FETCH_OPTIONS_INIT { GIT_FETCH_OPTIONS_VERSION, GIT_REMOTE_CALLBACKS_INIT, GIT_FETCH_PRUNE_UNSPECIFIED, 1, \ + GIT_REMOTE_DOWNLOAD_TAGS_UNSPECIFIED, GIT_PROXY_OPTIONS_INIT } /** * Initializes a `git_fetch_options` with default values. Equivalent to * creating an instance with GIT_FETCH_OPTIONS_INIT. * - * @param opts the `git_push_options` instance to initialize. + * @param opts the `git_fetch_options` instance to initialize. * @param version the version of the struct; you should pass * `GIT_FETCH_OPTIONS_VERSION` here. * @return Zero on success; -1 on failure. @@ -592,6 +600,11 @@ typedef struct { */ git_remote_callbacks callbacks; + /** + * Proxy options to use, by default no proxy is used. + */ + git_proxy_options proxy_opts; + /** * Extra headers for this push operation */ @@ -599,7 +612,7 @@ typedef struct { } git_push_options; #define GIT_PUSH_OPTIONS_VERSION 1 -#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION, 0, GIT_REMOTE_CALLBACKS_INIT } +#define GIT_PUSH_OPTIONS_INIT { GIT_PUSH_OPTIONS_VERSION, 0, GIT_REMOTE_CALLBACKS_INIT, GIT_PROXY_OPTIONS_INIT } /** * Initializes a `git_push_options` with default values. Equivalent to diff --git a/include/git2/repository.h b/include/git2/repository.h index 85b7e6861..3d70d1b89 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -95,11 +95,29 @@ GIT_EXTERN(int) git_repository_discover( * * GIT_REPOSITORY_OPEN_BARE - Open repository as a bare repo regardless * of core.bare config, and defer loading config file for faster setup. * Unlike `git_repository_open_bare`, this can follow gitlinks. + * * GIT_REPOSITORY_OPEN_NO_DOTGIT - Do not check for a repository by + * appending /.git to the start_path; only open the repository if + * start_path itself points to the git directory. + * * GIT_REPOSITORY_OPEN_FROM_ENV - Find and open a git repository, + * respecting the environment variables used by the git command-line + * tools. If set, `git_repository_open_ext` will ignore the other + * flags and the `ceiling_dirs` argument, and will allow a NULL `path` + * to use `GIT_DIR` or search from the current directory. The search + * for a repository will respect $GIT_CEILING_DIRECTORIES and + * $GIT_DISCOVERY_ACROSS_FILESYSTEM. The opened repository will + * respect $GIT_INDEX_FILE, $GIT_NAMESPACE, $GIT_OBJECT_DIRECTORY, and + * $GIT_ALTERNATE_OBJECT_DIRECTORIES. In the future, this flag will + * also cause `git_repository_open_ext` to respect $GIT_WORK_TREE and + * $GIT_COMMON_DIR; currently, `git_repository_open_ext` with this + * flag will error out if either $GIT_WORK_TREE or $GIT_COMMON_DIR is + * set. */ typedef enum { GIT_REPOSITORY_OPEN_NO_SEARCH = (1 << 0), GIT_REPOSITORY_OPEN_CROSS_FS = (1 << 1), GIT_REPOSITORY_OPEN_BARE = (1 << 2), + GIT_REPOSITORY_OPEN_NO_DOTGIT = (1 << 3), + GIT_REPOSITORY_OPEN_FROM_ENV = (1 << 4), } git_repository_open_flag_t; /** @@ -110,7 +128,8 @@ typedef enum { * see if a repo at this path could be opened. * @param 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. + * inside the working directory of the repository. May be NULL if + * flags is GIT_REPOSITORY_OPEN_FROM_ENV. * @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 diff --git a/include/git2/revwalk.h b/include/git2/revwalk.h index 2cc00536e..d9376ceea 100644 --- a/include/git2/revwalk.h +++ b/include/git2/revwalk.h @@ -25,17 +25,15 @@ GIT_BEGIN_DECL */ typedef enum { /** - * Sort the repository contents in no particular ordering; - * this sorting is arbitrary, implementation-specific - * and subject to change at any time. + * Sort the output with the same default time-order method from git. * This is the default sorting for new walkers. */ GIT_SORT_NONE = 0, /** - * Sort the repository contents in topological order - * (parents before children); this sorting mode - * can be combined with time sorting. + * Sort the repository contents in topological order (parents before + * children); this sorting mode can be combined with time sorting to + * produce git's "time-order". */ GIT_SORT_TOPOLOGICAL = 1 << 0, diff --git a/include/git2/signature.h b/include/git2/signature.h index feb1b4073..7a2a0238a 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -62,6 +62,19 @@ GIT_EXTERN(int) git_signature_now(git_signature **out, const char *name, const c */ GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo); +/** + * Create a new signature by parsing the given buffer, which is + * expected to be in the format "Real Name timestamp tzoffset", + * where `timestamp` is the number of seconds since the Unix epoch and + * `tzoffset` is the timezone offset in `hhmm` format (note the lack + * of a colon separator). + * + * @param out new signature + * @param buf signature string + * @return 0 on success, or an error code + */ +GIT_EXTERN(int) git_signature_from_buffer(git_signature **out, const char *buf); + /** * Create a copy of an existing signature. All internal strings are also * duplicated. diff --git a/include/git2/submodule.h b/include/git2/submodule.h index bc94eacaa..540ecf5c7 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -154,13 +154,19 @@ typedef struct git_submodule_update_options { * in the working directory for the newly cloned repository. */ unsigned int clone_checkout_strategy; + + /** + * Allow fetching from the submodule's default remote if the target + * commit isn't found. Enabled by default. + */ + int allow_fetch; } git_submodule_update_options; #define GIT_SUBMODULE_UPDATE_OPTIONS_VERSION 1 #define GIT_SUBMODULE_UPDATE_OPTIONS_INIT \ - { GIT_CHECKOUT_OPTIONS_VERSION, \ + { GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, \ { GIT_CHECKOUT_OPTIONS_VERSION, GIT_CHECKOUT_SAFE }, \ - GIT_FETCH_OPTIONS_INIT, GIT_CHECKOUT_SAFE } + GIT_FETCH_OPTIONS_INIT, GIT_CHECKOUT_SAFE, 1 } /** * Initializes a `git_submodule_update_options` with default values. @@ -176,7 +182,9 @@ GIT_EXTERN(int) git_submodule_update_init_options( /** * Update a submodule. This will clone a missing submodule and * checkout the subrepository to the commit specified in the index of - * containing repository. + * the containing repository. If the submodule repository doesn't contain + * the target commit (e.g. because fetchRecurseSubmodules isn't set), then + * the submodule is fetched using the fetch options supplied in options. * * @param submodule Submodule object * @param init If the submodule is not initialized, setting this flag to true diff --git a/include/git2/sys/merge.h b/include/git2/sys/merge.h new file mode 100644 index 000000000..031941042 --- /dev/null +++ b/include/git2/sys/merge.h @@ -0,0 +1,177 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_sys_git_merge_h__ +#define INCLUDE_sys_git_merge_h__ + +/** + * @file git2/sys/merge.h + * @brief Git merge driver backend and plugin routines + * @defgroup git_backend Git custom backend APIs + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +typedef struct git_merge_driver git_merge_driver; + +/** + * Look up a merge driver by name + * + * @param name The name of the merge driver + * @return Pointer to the merge driver object or NULL if not found + */ +GIT_EXTERN(git_merge_driver *) git_merge_driver_lookup(const char *name); + +#define GIT_MERGE_DRIVER_TEXT "text" +#define GIT_MERGE_DRIVER_BINARY "binary" +#define GIT_MERGE_DRIVER_UNION "union" + +/** + * A merge driver source represents the file to be merged + */ +typedef struct git_merge_driver_source git_merge_driver_source; + +/** Get the repository that the source data is coming from. */ +GIT_EXTERN(git_repository *) git_merge_driver_source_repo( + const git_merge_driver_source *src); + +/** Gets the ancestor of the file to merge. */ +GIT_EXTERN(git_index_entry *) git_merge_driver_source_ancestor( + const git_merge_driver_source *src); + +/** Gets the ours side of the file to merge. */ +GIT_EXTERN(git_index_entry *) git_merge_driver_source_ours( + const git_merge_driver_source *src); + +/** Gets the theirs side of the file to merge. */ +GIT_EXTERN(git_index_entry *) git_merge_driver_source_theirs( + const git_merge_driver_source *src); + +/** Gets the merge file options that the merge was invoked with */ +GIT_EXTERN(git_merge_file_options *) git_merge_driver_source_file_options( + const git_merge_driver_source *src); + + +/** + * Initialize callback on merge driver + * + * Specified as `driver.initialize`, this is an optional callback invoked + * before a merge driver is first used. It will be called once at most + * per library lifetime. + * + * If non-NULL, the merge driver's `initialize` callback will be invoked + * right before the first use of the driver, so you can defer expensive + * initialization operations (in case libgit2 is being used in a way that + * doesn't need the merge driver). + */ +typedef int (*git_merge_driver_init_fn)(git_merge_driver *self); + +/** + * Shutdown callback on merge driver + * + * Specified as `driver.shutdown`, this is an optional callback invoked + * when the merge driver is unregistered or when libgit2 is shutting down. + * It will be called once at most and should release resources as needed. + * This may be called even if the `initialize` callback was not made. + * + * Typically this function will free the `git_merge_driver` object itself. + */ +typedef void (*git_merge_driver_shutdown_fn)(git_merge_driver *self); + +/** + * Callback to perform the merge. + * + * Specified as `driver.apply`, this is the callback that actually does the + * merge. If it can successfully perform a merge, it should populate + * `path_out` with a pointer to the filename to accept, `mode_out` with + * the resultant mode, and `merged_out` with the buffer of the merged file + * and then return 0. If the driver returns `GIT_PASSTHROUGH`, then the + * default merge driver should instead be run. It can also return + * `GIT_EMERGECONFLICT` if the driver is not able to produce a merge result, + * and the file will remain conflicted. Any other errors will fail and + * return to the caller. + * + * The `filter_name` contains the name of the filter that was invoked, as + * specified by the file's attributes. + * + * The `src` contains the data about the file to be merged. + */ +typedef int (*git_merge_driver_apply_fn)( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src); + +/** + * Merge driver structure used to register custom merge drivers. + * + * To associate extra data with a driver, allocate extra data and put the + * `git_merge_driver` struct at the start of your data buffer, then cast + * the `self` pointer to your larger structure when your callback is invoked. + */ +struct git_merge_driver { + /** The `version` should be set to `GIT_MERGE_DRIVER_VERSION`. */ + unsigned int version; + + /** Called when the merge driver is first used for any file. */ + git_merge_driver_init_fn initialize; + + /** Called when the merge driver is unregistered from the system. */ + git_merge_driver_shutdown_fn shutdown; + + /** + * Called to merge the contents of a conflict. If this function + * returns `GIT_PASSTHROUGH` then the default (`text`) merge driver + * will instead be invoked. If this function returns + * `GIT_EMERGECONFLICT` then the file will remain conflicted. + */ + git_merge_driver_apply_fn apply; +}; + +#define GIT_MERGE_DRIVER_VERSION 1 + +/** + * Register a merge driver under a given name. + * + * As mentioned elsewhere, the initialize callback will not be invoked + * immediately. It is deferred until the driver is used in some way. + * + * Currently the merge driver registry is not thread safe, so any + * registering or deregistering of merge drivers must be done outside of + * any possible usage of the drivers (i.e. during application setup or + * shutdown). + * + * @param name The name of this driver to match an attribute. Attempting + * to register with an in-use name will return GIT_EEXISTS. + * @param driver The merge driver definition. This pointer will be stored + * as is by libgit2 so it must be a durable allocation (either + * static or on the heap). + * @return 0 on successful registry, error code <0 on failure + */ +GIT_EXTERN(int) git_merge_driver_register( + const char *name, git_merge_driver *driver); + +/** + * Remove the merge driver with the given name. + * + * Attempting to remove the builtin libgit2 merge drivers is not permitted + * and will return an error. + * + * Currently the merge driver registry is not thread safe, so any + * registering or deregistering of drivers must be done outside of any + * possible usage of the drivers (i.e. during application setup or shutdown). + * + * @param name The name under which the merge driver was registered + * @return 0 on success, error code <0 on failure + */ +GIT_EXTERN(int) git_merge_driver_unregister(const char *name); + +/** @} */ +GIT_END_DECL +#endif diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index e423a9236..9bcc50ddd 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -83,6 +83,17 @@ struct git_odb_backend { git_odb_writepack **, git_odb_backend *, git_odb *odb, git_transfer_progress_cb progress_cb, void *progress_payload); + /** + * "Freshens" an already existing object, updating its last-used + * time. This occurs when `git_odb_write` was called, but the + * object already existed (and will not be re-written). The + * underlying implementation may want to update last-used timestamps. + * + * If callers implement this, they should return `0` if the object + * exists and was freshened, and non-zero otherwise. + */ + int (* freshen)(git_odb_backend *, const git_oid *); + /** * Frees any resources held by the odb (including the `git_odb_backend` * itself). An odb backend implementation must provide this function. diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h new file mode 100644 index 000000000..3037b411c --- /dev/null +++ b/include/git2/sys/remote.h @@ -0,0 +1,16 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#ifndef INCLUDE_sys_git_transport_h +#define INCLUDE_sys_git_transport_h + +#include "git2/net.h" +#include "git2/types.h" + +GIT_BEGIN_DECL + +GIT_END_DECL diff --git a/include/git2/sys/stream.h b/include/git2/sys/stream.h index 2b4ff7fd8..eeeb68dae 100644 --- a/include/git2/sys/stream.h +++ b/include/git2/sys/stream.h @@ -9,6 +9,7 @@ #include "git2/common.h" #include "git2/types.h" +#include "git2/proxy.h" GIT_BEGIN_DECL @@ -32,7 +33,7 @@ typedef struct git_stream { int proxy_support; int (*connect)(struct git_stream *); int (*certificate)(git_cert **, struct git_stream *); - int (*set_proxy)(struct git_stream *, const char *proxy_url); + int (*set_proxy)(struct git_stream *, const git_proxy_options *proxy_opts); ssize_t (*read)(struct git_stream *, void *, size_t); ssize_t (*write)(struct git_stream *, const char *, size_t, int); int (*close)(struct git_stream *); diff --git a/include/git2/sys/time.h b/include/git2/sys/time.h new file mode 100644 index 000000000..e4f87e6e1 --- /dev/null +++ b/include/git2/sys/time.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_time_h__ +#define INCLUDE_git_time_h__ + +#include "git2/common.h" + +GIT_BEGIN_DECL + +/** + * Return a monotonic time value, useful for measuring running time + * and setting up timeouts. + * + * The returned value is an arbitrary point in time -- it can only be + * used when comparing it to another `git_time_monotonic` call. + * + * The time is returned in seconds, with a decimal fraction that differs + * on accuracy based on the underlying system, but should be least + * accurate to Nanoseconds. + * + * This function cannot fail. + */ +GIT_EXTERN(double) git_time_monotonic(void); + +GIT_END_DECL +#endif + diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index ce0234a18..60e38b21a 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -11,6 +11,7 @@ #include "git2/net.h" #include "git2/types.h" #include "git2/strarray.h" +#include "git2/proxy.h" /** * @file git2/sys/transport.h @@ -53,6 +54,7 @@ struct git_transport { const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, + const git_proxy_options *proxy_opts, int direction, int flags); @@ -65,7 +67,7 @@ struct git_transport { git_transport *transport); /* Executes the push whose context is in the git_push object. */ - int (*push)(git_transport *transport, git_push *push, const git_remote_callbacks *callbacks); + int(*push)(git_transport *transport, git_push *push, const git_remote_callbacks *callbacks); /* This function may be called after a successful call to connect(), when * the direction is FETCH. The function performs a negotiation to calculate diff --git a/include/git2/tag.h b/include/git2/tag.h index c822cee7c..cb95fb5ef 100644 --- a/include/git2/tag.h +++ b/include/git2/tag.h @@ -347,6 +347,15 @@ GIT_EXTERN(int) git_tag_peel( git_object **tag_target_out, const git_tag *tag); +/** + * Create an in-memory copy of a tag. The copy must be explicitly + * free'd or it will leak. + * + * @param out Pointer to store the copy of the tag + * @param source Original tag to copy + */ +GIT_EXTERN(int) git_tag_dup(git_tag **out, git_tag *source); + /** @} */ GIT_END_DECL #endif diff --git a/include/git2/transaction.h b/include/git2/transaction.h index 64abb0c69..00ca13993 100644 --- a/include/git2/transaction.h +++ b/include/git2/transaction.h @@ -8,6 +8,14 @@ #define INCLUDE_git_transaction_h__ #include "common.h" + +/** + * @file git2/transaction.h + * @brief Git transactional reference routines + * @defgroup git_transaction Git transactional reference routines + * @ingroup Git + * @{ + */ GIT_BEGIN_DECL /** @@ -107,5 +115,6 @@ GIT_EXTERN(int) git_transaction_commit(git_transaction *tx); */ GIT_EXTERN(void) git_transaction_free(git_transaction *tx); +/** @} */ GIT_END_DECL #endif diff --git a/include/git2/tree.h b/include/git2/tree.h index 550a44857..2e4735c4b 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -409,6 +409,61 @@ GIT_EXTERN(int) git_tree_walk( git_treewalk_cb callback, void *payload); +/** + * Create an in-memory copy of a tree. The copy must be explicitly + * free'd or it will leak. + * + * @param out Pointer to store the copy of the tree + * @param source Original tree to copy + */ +GIT_EXTERN(int) git_tree_dup(git_tree **out, git_tree *source); + +/** + * The kind of update to perform + */ +typedef enum { + /** Update or insert an entry at the specified path */ + GIT_TREE_UPDATE_UPSERT, + /** Remove an entry from the specified path */ + GIT_TREE_UPDATE_REMOVE, +} git_tree_update_t; + +/** + * An action to perform during the update of a tree + */ +typedef struct { + /** Update action. If it's an removal, only the path is looked at */ + git_tree_update_t action; + /** The entry's id */ + git_oid id; + /** The filemode/kind of object */ + git_filemode_t filemode; + /** The full path from the root tree */ + const char *path; +} git_tree_update; + +/** + * Create a tree based on another one with the specified modifications + * + * Given the `baseline` perform the changes described in the list of + * `updates` and create a new tree. + * + * This function is optimized for common file/directory addition, removal and + * replacement in trees. It is much more efficient than reading the tree into a + * `git_index` and modifying that, but in exchange it is not as flexible. + * + * Deleting and adding the same entry is undefined behaviour, changing + * a tree to a blob or viceversa is not supported. + * + * @param out id of the new tree + * @param repo the repository in which to create the tree, must be the + * same as for `baseline` + * @param baseline the tree to base these changes on + * @param nupdates the number of elements in the update list + * @param updates the list of updates to perform + */ +GIT_EXTERN(int) git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates); + /** @} */ GIT_END_DECL diff --git a/include/git2/version.h b/include/git2/version.h index 5e060325c..d190893fe 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -7,12 +7,12 @@ #ifndef INCLUDE_git_version_h__ #define INCLUDE_git_version_h__ -#define LIBGIT2_VERSION "0.24.5" +#define LIBGIT2_VERSION "0.25.1" #define LIBGIT2_VER_MAJOR 0 -#define LIBGIT2_VER_MINOR 24 -#define LIBGIT2_VER_REVISION 5 +#define LIBGIT2_VER_MINOR 25 +#define LIBGIT2_VER_REVISION 1 #define LIBGIT2_VER_PATCH 0 -#define LIBGIT2_SOVERSION 24 +#define LIBGIT2_SOVERSION 25 #endif diff --git a/script/appveyor-mingw.sh b/script/appveyor-mingw.sh index 48e0bad0a..198801875 100755 --- a/script/appveyor-mingw.sh +++ b/script/appveyor-mingw.sh @@ -1,9 +1,7 @@ #!/bin/sh set -e cd `dirname "$0"`/.. -if [ "$ARCH" = "32" ]; then - echo 'C:\MinGW\ /MinGW' > /etc/fstab -elif [ "$ARCH" = "i686" ]; then +if [ "$ARCH" = "i686" ]; then f=i686-4.9.2-release-win32-sjlj-rt_v3-rev1.7z if ! [ -e $f ]; then curl -LsSO http://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/4.9.2/threads-win32/sjlj/$f diff --git a/script/cibuild.sh b/script/cibuild.sh index 6709b38f9..403df223e 100755 --- a/script/cibuild.sh +++ b/script/cibuild.sh @@ -1,5 +1,7 @@ #!/bin/sh +set -x + if [ -n "$COVERITY" ]; then ./script/coverity.sh; @@ -10,10 +12,15 @@ if [ "$TRAVIS_OS_NAME" = "osx" ]; then export PKG_CONFIG_PATH=$(ls -d /usr/local/Cellar/{curl,zlib}/*/lib/pkgconfig | paste -s -d':' -) fi +# Should we ask Travis to cache this file? +curl -L https://github.com/ethomson/poxyproxy/releases/download/v0.1.0/poxyproxy-0.1.0.jar >poxyproxy.jar || exit $? +# Run this early so we know it's ready by the time we need it +java -jar poxyproxy.jar -d --port 8080 --credentials foo:bar & + mkdir _build cd _build # shellcheck disable=SC2086 -cmake .. -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? +cmake .. -DBUILD_EXAMPLES=ON -DCMAKE_INSTALL_PREFIX=../_install $OPTIONS || exit $? make -j2 install || exit $? # If this platform doesn't support test execution, bail out now @@ -58,12 +65,22 @@ export GITTEST_REMOTE_SSH_KEY="$HOME/.ssh/id_rsa" export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub" export GITTEST_REMOTE_SSH_PASSPHRASE="" + if [ -e ./libgit2_clar ]; then ./libgit2_clar -sonline::push -sonline::clone::ssh_cert && ./libgit2_clar -sonline::clone::ssh_with_paths || exit $? if [ "$TRAVIS_OS_NAME" = "linux" ]; then ./libgit2_clar -sonline::clone::cred_callback || exit $? fi + + # Use the proxy we started at the beginning + export GITTEST_REMOTE_PROXY_URL="http://foo:bar@localhost:8080/" + ./libgit2_clar -sonline::clone::proxy_credentials_in_url || exit $? + export GITTEST_REMOTE_PROXY_URL="http://localhost:8080/" + export GITTEST_REMOTE_PROXY_USER="foo" + export GITTEST_REMOTE_PROXY_PASS="bar" + ./libgit2_clar -sonline::clone::proxy_credentials_request || exit $? + fi export GITTEST_REMOTE_URL="https://github.com/libgit2/non-existent" diff --git a/script/coverity.sh b/script/coverity.sh index 7fe9eb4c7..5fe16c031 100755 --- a/script/coverity.sh +++ b/script/coverity.sh @@ -1,23 +1,22 @@ #!/bin/bash set -e +# Only run this on our branches +echo "Branch: $TRAVIS_BRANCH | Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG" +if [ "$TRAVIS_BRANCH" != "master" -o "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ]; +then + echo "Only analyzing the 'master' brach of the main repository." + exit 0 +fi + # Environment check [ -z "$COVERITY_TOKEN" ] && echo "Need to set a coverity token" && exit 1 -# Only run this on our branches -echo "Pull request: $TRAVIS_PULL_REQUEST | Slug: $TRAVIS_REPO_SLUG" -if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_REPO_SLUG" != "libgit2/libgit2" ]; -then - echo "Only analyzing 'development' on the main repo." - exit 0 -fi - -COV_VERSION=6.6.1 case $(uname -m) in i?86) BITS=32 ;; amd64|x86_64) BITS=64 ;; esac -SCAN_TOOL=https://scan.coverity.com/download/linux-${BITS} +SCAN_TOOL=https://scan.coverity.com/download/cxx/linux${BITS} TOOL_BASE=$(pwd)/_coverity-scan # Install coverity tools diff --git a/script/toolchain-mingw32.cmake b/script/toolchain-mingw32.cmake deleted file mode 100644 index 2536b01c3..000000000 --- a/script/toolchain-mingw32.cmake +++ /dev/null @@ -1,13 +0,0 @@ -# CMake toolchain file for Win32 cross-compile -SET(CMAKE_SYSTEM_NAME Windows) - -SET(CMAKE_C_COMPILER i586-mingw32msvc-gcc) -SET(CMAKE_RC_COMPILER i586-mingw32msvc-windres) - -SET(CMAKE_FIND_ROOT_PATH /usr/i586-mingw32msvc) - -SET(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) -SET(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) -SET(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) - -SET(ENV{PKG_CONFIG_LIBDIR} ${CMAKE_FIND_ROOT_PATH}/lib/pkgconfig) diff --git a/script/user_model.c b/script/user_model.c new file mode 100644 index 000000000..a933d735c --- /dev/null +++ b/script/user_model.c @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +void *realloc(void *ptr, size_t size); +void *memmove(void *dest, const void *src, size_t n); +size_t strlen(const char *s); + +typedef struct va_list_str *va_list; + +typedef struct git_vector { + void **contents; + size_t length; +} git_vector; + +typedef struct git_buf { + char *ptr; + size_t asize, size; +} git_buf; + +int git_vector_insert(git_vector *v, void *element) +{ + if (!v) + __coverity_panic__(); + + v->contents = realloc(v->contents, ++v->length); + if (!v->contents) + __coverity_panic__(); + v->contents[v->length] = element; + + return 0; +} + +int git_buf_len(const struct git_buf *buf) +{ + return strlen(buf->ptr); +} + +int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) +{ + char ch, *s; + size_t len; + + __coverity_string_null_sink__(format); + __coverity_string_size_sink__(format); + + ch = *format; + ch = *(char *)ap; + + buf->ptr = __coverity_alloc__(len); + __coverity_writeall__(buf->ptr); + buf->size = len; + + return 0; +} + +int git_buf_put(git_buf *buf, const char *data, size_t len) +{ + buf->ptr = __coverity_alloc__(buf->size + len + 1); + memmove(buf->ptr + buf->size, data, len); + buf->size += len; + buf->ptr[buf->size + len] = 0; + return 0; +} + +int git_buf_set(git_buf *buf, const void *data, size_t len) +{ + buf->ptr = __coverity_alloc__(len + 1); + memmove(buf->ptr, data, len); + buf->size = len + 1; + return 0; +} diff --git a/src/annotated_commit.c b/src/annotated_commit.c index e53b95dee..c2c770cba 100644 --- a/src/annotated_commit.c +++ b/src/annotated_commit.c @@ -20,41 +20,104 @@ static int annotated_commit_init( git_annotated_commit **out, - git_repository *repo, - const git_oid *id, - const char *ref_name, - const char *remote_url) + git_commit *commit, + const char *description) { git_annotated_commit *annotated_commit; - git_commit *commit = NULL; int error = 0; - assert(out && id); + assert(out && commit); *out = NULL; - if ((error = git_commit_lookup(&commit, repo, id)) < 0 || - (error = git_annotated_commit_from_commit(&annotated_commit, - commit)) < 0) + annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); + GITERR_CHECK_ALLOC(annotated_commit); + + annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; + + if ((error = git_commit_dup(&annotated_commit->commit, commit)) < 0) goto done; - if (ref_name) { - annotated_commit->ref_name = git__strdup(ref_name); - GITERR_CHECK_ALLOC(annotated_commit->ref_name); - } + git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); + annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; - if (remote_url) { - annotated_commit->remote_url = git__strdup(remote_url); - GITERR_CHECK_ALLOC(annotated_commit->remote_url); - } + if (!description) + description = annotated_commit->id_str; - *out = annotated_commit; + annotated_commit->description = git__strdup(description); + GITERR_CHECK_ALLOC(annotated_commit->description); + +done: + if (!error) + *out = annotated_commit; + + return error; +} + +static int annotated_commit_init_from_id( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id, + const char *description) +{ + git_commit *commit = NULL; + int error = 0; + + assert(out && repo && id); + + *out = NULL; + + if ((error = git_commit_lookup(&commit, repo, id)) < 0) + goto done; + + error = annotated_commit_init(out, commit, description); done: git_commit_free(commit); return error; } +int git_annotated_commit_lookup( + git_annotated_commit **out, + git_repository *repo, + const git_oid *id) +{ + return annotated_commit_init_from_id(out, repo, id, NULL); +} + +int git_annotated_commit_from_commit( + git_annotated_commit **out, + git_commit *commit) +{ + return annotated_commit_init(out, commit, NULL); +} + +int git_annotated_commit_from_revspec( + git_annotated_commit **out, + git_repository *repo, + const char *revspec) +{ + git_object *obj, *commit; + int error; + + assert(out && repo && revspec); + + if ((error = git_revparse_single(&obj, repo, revspec)) < 0) + return error; + + if ((error = git_object_peel(&commit, obj, GIT_OBJ_COMMIT))) { + git_object_free(obj); + return error; + } + + error = annotated_commit_init(out, (git_commit *)commit, revspec); + + git_object_free(obj); + git_object_free(commit); + + return error; +} + int git_annotated_commit_from_ref( git_annotated_commit **out, git_repository *repo, @@ -70,8 +133,15 @@ int git_annotated_commit_from_ref( if ((error = git_reference_resolve(&resolved, ref)) < 0) return error; - error = annotated_commit_init(out, repo, git_reference_target(resolved), - git_reference_name(ref), NULL); + error = annotated_commit_init_from_id(out, + repo, + git_reference_target(resolved), + git_reference_name(ref)); + + if (!error) { + (*out)->ref_name = git__strdup(git_reference_name(ref)); + GITERR_CHECK_ALLOC((*out)->ref_name); + } git_reference_free(resolved); return error; @@ -97,41 +167,6 @@ int git_annotated_commit_from_head( return error; } -int git_annotated_commit_from_commit( - git_annotated_commit **out, - git_commit *commit) -{ - git_annotated_commit *annotated_commit; - - assert(out && commit); - - *out = NULL; - - annotated_commit = git__calloc(1, sizeof(git_annotated_commit)); - GITERR_CHECK_ALLOC(annotated_commit); - - annotated_commit->type = GIT_ANNOTATED_COMMIT_REAL; - - git_cached_obj_incref(commit); - annotated_commit->commit = commit; - - git_oid_fmt(annotated_commit->id_str, git_commit_id(commit)); - annotated_commit->id_str[GIT_OID_HEXSZ] = '\0'; - - *out = annotated_commit; - return 0; -} - -int git_annotated_commit_lookup( - git_annotated_commit **out, - git_repository *repo, - const git_oid *id) -{ - assert(out && repo && id); - - return annotated_commit_init(out, repo, id, NULL, NULL); -} - int git_annotated_commit_from_fetchhead( git_annotated_commit **out, git_repository *repo, @@ -141,33 +176,16 @@ int git_annotated_commit_from_fetchhead( { assert(repo && id && branch_name && remote_url); - return annotated_commit_init(out, repo, id, branch_name, remote_url); -} + if (annotated_commit_init_from_id(out, repo, id, branch_name) < 0) + return -1; -int git_annotated_commit_from_revspec( - git_annotated_commit **out, - git_repository *repo, - const char *revspec) -{ - git_object *obj, *commit; - int error; + (*out)->ref_name = git__strdup(branch_name); + GITERR_CHECK_ALLOC((*out)->ref_name); - assert(out && repo && revspec); + (*out)->remote_url = git__strdup(remote_url); + GITERR_CHECK_ALLOC((*out)->remote_url); - if ((error = git_revparse_single(&obj, repo, revspec)) < 0) - return error; - - if ((error = git_object_peel(&commit, obj, GIT_OBJ_COMMIT))) { - git_object_free(obj); - return error; - } - - error = annotated_commit_init(out, repo, git_object_id(commit), revspec, NULL); - - git_object_free(obj); - git_object_free(commit); - - return error; + return 0; } @@ -187,8 +205,9 @@ void git_annotated_commit_free(git_annotated_commit *annotated_commit) case GIT_ANNOTATED_COMMIT_REAL: git_commit_free(annotated_commit->commit); git_tree_free(annotated_commit->tree); - git__free(annotated_commit->ref_name); - git__free(annotated_commit->remote_url); + git__free((char *)annotated_commit->description); + git__free((char *)annotated_commit->ref_name); + git__free((char *)annotated_commit->remote_url); break; case GIT_ANNOTATED_COMMIT_VIRTUAL: git_index_free(annotated_commit->index); diff --git a/src/annotated_commit.h b/src/annotated_commit.h index cbb88fd22..3ac8b5f69 100644 --- a/src/annotated_commit.h +++ b/src/annotated_commit.h @@ -33,8 +33,11 @@ struct git_annotated_commit { git_index *index; git_array_oid_t parents; - char *ref_name; - char *remote_url; + /* how this commit was looked up */ + const char *description; + + const char *ref_name; + const char *remote_url; char id_str[GIT_OID_HEXSZ+1]; }; diff --git a/src/apply.c b/src/apply.c new file mode 100644 index 000000000..635934299 --- /dev/null +++ b/src/apply.c @@ -0,0 +1,377 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2/patch.h" +#include "git2/filter.h" +#include "array.h" +#include "patch.h" +#include "fileops.h" +#include "apply.h" +#include "delta.h" +#include "zstream.h" + +#define apply_err(...) \ + ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) + +typedef struct { + /* The lines that we allocate ourself are allocated out of the pool. + * (Lines may have been allocated out of the diff.) + */ + git_pool pool; + git_vector lines; +} patch_image; + +static void patch_line_init( + git_diff_line *out, + const char *in, + size_t in_len, + size_t in_offset) +{ + out->content = in; + out->content_len = in_len; + out->content_offset = in_offset; +} + +#define PATCH_IMAGE_INIT { GIT_POOL_INIT, GIT_VECTOR_INIT } + +static int patch_image_init_fromstr( + patch_image *out, const char *in, size_t in_len) +{ + git_diff_line *line; + const char *start, *end; + + memset(out, 0x0, sizeof(patch_image)); + + git_pool_init(&out->pool, sizeof(git_diff_line)); + + for (start = in; start < in + in_len; start = end) { + end = memchr(start, '\n', in_len); + + if (end == NULL) + end = in + in_len; + + else if (end < in + in_len) + end++; + + line = git_pool_mallocz(&out->pool, 1); + GITERR_CHECK_ALLOC(line); + + if (git_vector_insert(&out->lines, line) < 0) + return -1; + + patch_line_init(line, start, (end - start), (start - in)); + } + + return 0; +} + +static void patch_image_free(patch_image *image) +{ + if (image == NULL) + return; + + git_pool_clear(&image->pool); + git_vector_free(&image->lines); +} + +static bool match_hunk( + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + bool match = 0; + size_t i; + + /* Ensure this hunk is within the image boundaries. */ + if (git_vector_length(&preimage->lines) + linenum > + git_vector_length(&image->lines)) + return 0; + + match = 1; + + /* Check exact match. */ + for (i = 0; i < git_vector_length(&preimage->lines); i++) { + git_diff_line *preimage_line = git_vector_get(&preimage->lines, i); + git_diff_line *image_line = git_vector_get(&image->lines, linenum + i); + + if (preimage_line->content_len != image_line->content_len || + memcmp(preimage_line->content, image_line->content, image_line->content_len) != 0) { + match = 0; + break; + } + } + + return match; +} + +static bool find_hunk_linenum( + size_t *out, + patch_image *image, + patch_image *preimage, + size_t linenum) +{ + size_t max = git_vector_length(&image->lines); + bool match; + + if (linenum > max) + linenum = max; + + match = match_hunk(image, preimage, linenum); + + *out = linenum; + return match; +} + +static int update_hunk( + patch_image *image, + unsigned int linenum, + patch_image *preimage, + patch_image *postimage) +{ + size_t postlen = git_vector_length(&postimage->lines); + size_t prelen = git_vector_length(&preimage->lines); + size_t i; + int error = 0; + + if (postlen > prelen) + error = git_vector_insert_null( + &image->lines, linenum, (postlen - prelen)); + else if (prelen > postlen) + error = git_vector_remove_range( + &image->lines, linenum, (prelen - postlen)); + + if (error) { + giterr_set_oom(); + return -1; + } + + for (i = 0; i < git_vector_length(&postimage->lines); i++) { + image->lines.contents[linenum + i] = + git_vector_get(&postimage->lines, i); + } + + return 0; +} + +static int apply_hunk( + patch_image *image, + git_patch *patch, + git_patch_hunk *hunk) +{ + patch_image preimage = PATCH_IMAGE_INIT, postimage = PATCH_IMAGE_INIT; + size_t line_num, i; + int error = 0; + + for (i = 0; i < hunk->line_count; i++) { + size_t linenum = hunk->line_start + i; + git_diff_line *line = git_array_get(patch->lines, linenum); + + if (!line) { + error = apply_err("Preimage does not contain line %"PRIuZ, linenum); + goto done; + } + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_DELETION) { + if ((error = git_vector_insert(&preimage.lines, line)) < 0) + goto done; + } + + if (line->origin == GIT_DIFF_LINE_CONTEXT || + line->origin == GIT_DIFF_LINE_ADDITION) { + if ((error = git_vector_insert(&postimage.lines, line)) < 0) + goto done; + } + } + + line_num = hunk->hunk.new_start ? hunk->hunk.new_start - 1 : 0; + + if (!find_hunk_linenum(&line_num, image, &preimage, line_num)) { + error = apply_err("Hunk at line %d did not apply", + hunk->hunk.new_start); + goto done; + } + + error = update_hunk(image, line_num, &preimage, &postimage); + +done: + patch_image_free(&preimage); + patch_image_free(&postimage); + + return error; +} + +static int apply_hunks( + git_buf *out, + const char *source, + size_t source_len, + git_patch *patch) +{ + git_patch_hunk *hunk; + git_diff_line *line; + patch_image image; + size_t i; + int error = 0; + + if ((error = patch_image_init_fromstr(&image, source, source_len)) < 0) + goto done; + + git_array_foreach(patch->hunks, i, hunk) { + if ((error = apply_hunk(&image, patch, hunk)) < 0) + goto done; + } + + git_vector_foreach(&image.lines, i, line) + git_buf_put(out, line->content, line->content_len); + +done: + patch_image_free(&image); + + return error; +} + +static int apply_binary_delta( + git_buf *out, + const char *source, + size_t source_len, + git_diff_binary_file *binary_file) +{ + git_buf inflated = GIT_BUF_INIT; + int error = 0; + + /* no diff means identical contents */ + if (binary_file->datalen == 0) + return git_buf_put(out, source, source_len); + + error = git_zstream_inflatebuf(&inflated, + binary_file->data, binary_file->datalen); + + if (!error && inflated.size != binary_file->inflatedlen) { + error = apply_err("inflated delta does not match expected length"); + git_buf_free(out); + } + + if (error < 0) + goto done; + + if (binary_file->type == GIT_DIFF_BINARY_DELTA) { + void *data; + size_t data_len; + + error = git_delta_apply(&data, &data_len, (void *)source, source_len, + (void *)inflated.ptr, inflated.size); + + out->ptr = data; + out->size = data_len; + out->asize = data_len; + } + else if (binary_file->type == GIT_DIFF_BINARY_LITERAL) { + git_buf_swap(out, &inflated); + } + else { + error = apply_err("unknown binary delta type"); + goto done; + } + +done: + git_buf_free(&inflated); + return error; +} + +static int apply_binary( + git_buf *out, + const char *source, + size_t source_len, + git_patch *patch) +{ + git_buf reverse = GIT_BUF_INIT; + int error = 0; + + if (!patch->binary.contains_data) { + error = apply_err("patch does not contain binary data"); + goto done; + } + + if (!patch->binary.old_file.datalen && !patch->binary.new_file.datalen) + goto done; + + /* first, apply the new_file delta to the given source */ + if ((error = apply_binary_delta(out, source, source_len, + &patch->binary.new_file)) < 0) + goto done; + + /* second, apply the old_file delta to sanity check the result */ + if ((error = apply_binary_delta(&reverse, out->ptr, out->size, + &patch->binary.old_file)) < 0) + goto done; + + if (source_len != reverse.size || + memcmp(source, reverse.ptr, source_len) != 0) { + error = apply_err("binary patch did not apply cleanly"); + goto done; + } + +done: + if (error < 0) + git_buf_free(out); + + git_buf_free(&reverse); + return error; +} + +int git_apply__patch( + git_buf *contents_out, + char **filename_out, + unsigned int *mode_out, + const char *source, + size_t source_len, + git_patch *patch) +{ + char *filename = NULL; + unsigned int mode = 0; + int error = 0; + + assert(contents_out && filename_out && mode_out && (source || !source_len) && patch); + + *filename_out = NULL; + *mode_out = 0; + + if (patch->delta->status != GIT_DELTA_DELETED) { + const git_diff_file *newfile = &patch->delta->new_file; + + filename = git__strdup(newfile->path); + mode = newfile->mode ? + newfile->mode : GIT_FILEMODE_BLOB; + } + + if (patch->delta->flags & GIT_DIFF_FLAG_BINARY) + error = apply_binary(contents_out, source, source_len, patch); + else if (patch->hunks.size) + error = apply_hunks(contents_out, source, source_len, patch); + else + error = git_buf_put(contents_out, source, source_len); + + if (error) + goto done; + + if (patch->delta->status == GIT_DELTA_DELETED && + git_buf_len(contents_out) > 0) { + error = apply_err("removal patch leaves file contents"); + goto done; + } + + *filename_out = filename; + *mode_out = mode; + +done: + if (error < 0) + git__free(filename); + + return error; +} diff --git a/src/apply.h b/src/apply.h new file mode 100644 index 000000000..96e0f55b5 --- /dev/null +++ b/src/apply.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_apply_h__ +#define INCLUDE_apply_h__ + +#include "git2/patch.h" +#include "buffer.h" + +extern int git_apply__patch( + git_buf *out, + char **filename, + unsigned int *mode, + const char *source, + size_t source_len, + git_patch *patch); + +#endif diff --git a/src/array.h b/src/array.h index 78d321e82..1d8a01c96 100644 --- a/src/array.h +++ b/src/array.h @@ -85,7 +85,6 @@ on_oom: #define git_array_foreach(a, i, element) \ for ((i) = 0; (i) < (a).size && ((element) = &(a).ptr[(i)]); (i)++) - GIT_INLINE(int) git_array__search( size_t *out, void *array_ptr, diff --git a/src/blob.c b/src/blob.c index ad0f4ac62..1926c9e58 100644 --- a/src/blob.c +++ b/src/blob.c @@ -274,66 +274,98 @@ int git_blob_create_fromdisk( return error; } -#define BUFFER_SIZE 4096 +typedef struct { + git_writestream parent; + git_filebuf fbuf; + git_repository *repo; + char *hintpath; +} blob_writestream; -int git_blob_create_fromchunks( - git_oid *id, - git_repository *repo, - const char *hintpath, - int (*source_cb)(char *content, size_t max_length, void *payload), - void *payload) +static int blob_writestream_close(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + return 0; +} + +static void blob_writestream_free(git_writestream *_stream) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + git_filebuf_cleanup(&stream->fbuf); + git__free(stream->hintpath); + git__free(stream); +} + +static int blob_writestream_write(git_writestream *_stream, const char *buffer, size_t len) +{ + blob_writestream *stream = (blob_writestream *) _stream; + + return git_filebuf_write(&stream->fbuf, buffer, len); +} + +int git_blob_create_fromstream(git_writestream **out, git_repository *repo, const char *hintpath) { int error; - char *content = NULL; - git_filebuf file = GIT_FILEBUF_INIT; git_buf path = GIT_BUF_INIT; + blob_writestream *stream; - assert(id && repo && source_cb); + assert(out && repo); - if ((error = git_buf_joinpath( - &path, git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) - goto cleanup; + stream = git__calloc(1, sizeof(blob_writestream)); + GITERR_CHECK_ALLOC(stream); - content = git__malloc(BUFFER_SIZE); - GITERR_CHECK_ALLOC(content); - - if ((error = git_filebuf_open( - &file, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, 0666)) < 0) - goto cleanup; - - while (1) { - int read_bytes = source_cb(content, BUFFER_SIZE, payload); - - if (!read_bytes) - break; - - if (read_bytes > BUFFER_SIZE) { - giterr_set(GITERR_OBJECT, "Invalid chunk size while creating blob"); - error = GIT_EBUFS; - } else if (read_bytes < 0) { - error = giterr_set_after_callback(read_bytes); - } else { - error = git_filebuf_write(&file, content, read_bytes); - } - - if (error < 0) - goto cleanup; + if (hintpath) { + stream->hintpath = git__strdup(hintpath); + GITERR_CHECK_ALLOC(stream->hintpath); } - if ((error = git_filebuf_flush(&file)) < 0) + stream->repo = repo; + stream->parent.write = blob_writestream_write; + stream->parent.close = blob_writestream_close; + stream->parent.free = blob_writestream_free; + + if ((error = git_buf_joinpath(&path, + git_repository_path(repo), GIT_OBJECTS_DIR "streamed")) < 0) goto cleanup; - error = git_blob__create_from_paths( - id, NULL, repo, file.path_lock, hintpath, 0, hintpath != NULL); + if ((error = git_filebuf_open_withsize(&stream->fbuf, git_buf_cstr(&path), GIT_FILEBUF_TEMPORARY, + 0666, 2 * 1024 * 1024)) < 0) + goto cleanup; + + *out = (git_writestream *) stream; cleanup: - git_buf_free(&path); - git_filebuf_cleanup(&file); - git__free(content); + if (error < 0) + blob_writestream_free((git_writestream *) stream); + git_buf_free(&path); return error; } +int git_blob_create_fromstream_commit(git_oid *out, git_writestream *_stream) +{ + int error; + blob_writestream *stream = (blob_writestream *) _stream; + + /* + * We can make this more officient by avoiding writing to + * disk, but for now let's re-use the helper functions we + * have. + */ + if ((error = git_filebuf_flush(&stream->fbuf)) < 0) + goto cleanup; + + error = git_blob__create_from_paths(out, NULL, stream->repo, stream->fbuf.path_lock, + stream->hintpath, 0, !!stream->hintpath); + +cleanup: + blob_writestream_free(_stream); + return error; + +} + int git_blob_is_binary(const git_blob *blob) { git_buf content = GIT_BUF_INIT; diff --git a/src/branch.c b/src/branch.c index 0dcc14c29..8d1ed6577 100644 --- a/src/branch.c +++ b/src/branch.c @@ -58,16 +58,17 @@ static int create_branch( const char *from, int force) { - int is_head = 0; + int is_unmovable_head = 0; git_reference *branch = NULL; git_buf canonical_branch_name = GIT_BUF_INIT, log_message = GIT_BUF_INIT; int error = -1; + int bare = git_repository_is_bare(repository); assert(branch_name && commit && ref_out); assert(git_object_owner((const git_object *)commit) == repository); - if (force && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { + if (force && !bare && git_branch_lookup(&branch, repository, branch_name, GIT_BRANCH_LOCAL) == 0) { error = git_branch_is_head(branch); git_reference_free(branch); branch = NULL; @@ -75,10 +76,10 @@ static int create_branch( if (error < 0) goto cleanup; - is_head = error; + is_unmovable_head = error; } - if (is_head && force) { + if (is_unmovable_head && force) { giterr_set(GITERR_REFERENCE, "Cannot force update branch '%s' as it is " "the current HEAD of the repository.", branch_name); error = -1; @@ -121,7 +122,8 @@ int git_branch_create_from_annotated( const git_annotated_commit *commit, int force) { - return create_branch(ref_out, repository, branch_name, commit->commit, commit->ref_name, force); + return create_branch(ref_out, + repository, branch_name, commit->commit, commit->description, force); } int git_branch_delete(git_reference *branch) diff --git a/src/buffer.c b/src/buffer.c index 1a5809cca..d135ebe4a 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -273,42 +273,51 @@ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len) return 0; } -/* The inverse of base64_encode, offset by '+' == 43. */ +/* The inverse of base64_encode */ static const int8_t base64_decode[] = { - 62, - -1, -1, -1, - 63, - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, - -1, -1, -1, 0, -1, -1, -1, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, - 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, - -1, -1, -1, -1, -1, -1, - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; -#define BASE64_DECODE_VALUE(c) (((c) < 43 || (c) > 122) ? -1 : base64_decode[c - 43]) - int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len) { size_t i; int8_t a, b, c, d; size_t orig_size = buf->size, new_size; + if (len % 4) { + giterr_set(GITERR_INVALID, "invalid base64 input"); + return -1; + } + assert(len % 4 == 0); GITERR_CHECK_ALLOC_ADD(&new_size, (len / 4 * 3), buf->size); GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1); ENSURE_SIZE(buf, new_size); for (i = 0; i < len; i += 4) { - if ((a = BASE64_DECODE_VALUE(base64[i])) < 0 || - (b = BASE64_DECODE_VALUE(base64[i+1])) < 0 || - (c = BASE64_DECODE_VALUE(base64[i+2])) < 0 || - (d = BASE64_DECODE_VALUE(base64[i+3])) < 0) { + if ((a = base64_decode[(unsigned char)base64[i]]) < 0 || + (b = base64_decode[(unsigned char)base64[i+1]]) < 0 || + (c = base64_decode[(unsigned char)base64[i+2]]) < 0 || + (d = base64_decode[(unsigned char)base64[i+3]]) < 0) { buf->size = orig_size; buf->ptr[buf->size] = '\0'; - giterr_set(GITERR_INVALID, "Invalid base64 input"); + giterr_set(GITERR_INVALID, "invalid base64 input"); return -1; } @@ -321,7 +330,7 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len) return 0; } -static const char b85str[] = +static const char base85_encode[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) @@ -351,7 +360,7 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) int val = acc % 85; acc /= 85; - b85[i] = b85str[val]; + b85[i] = base85_encode[val]; } for (i = 0; i < 5; i++) @@ -363,6 +372,88 @@ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len) return 0; } +/* The inverse of base85_encode */ +static const int8_t base85_decode[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 63, -1, 64, 65, 66, 67, -1, 68, 69, 70, 71, -1, 72, -1, -1, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -1, 73, 74, 75, 76, 77, + 78, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, 79, 80, + 81, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 82, 83, 84, 85, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +int git_buf_decode_base85( + git_buf *buf, + const char *base85, + size_t base85_len, + size_t output_len) +{ + size_t orig_size = buf->size, new_size; + + if (base85_len % 5 || + output_len > base85_len * 4 / 5) { + giterr_set(GITERR_INVALID, "invalid base85 input"); + return -1; + } + + GITERR_CHECK_ALLOC_ADD(&new_size, output_len, buf->size); + GITERR_CHECK_ALLOC_ADD(&new_size, new_size, 1); + ENSURE_SIZE(buf, new_size); + + while (output_len) { + unsigned acc = 0; + int de, cnt = 4; + unsigned char ch; + do { + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + acc = acc * 85 + de; + } while (--cnt); + ch = *base85++; + de = base85_decode[ch]; + if (--de < 0) + goto on_error; + + /* Detect overflow. */ + if (0xffffffff / 85 < acc || + 0xffffffff - de < (acc *= 85)) + goto on_error; + + acc += de; + + cnt = (output_len < 4) ? output_len : 4; + output_len -= cnt; + do { + acc = (acc << 8) | (acc >> 24); + buf->ptr[buf->size++] = acc; + } while (--cnt); + } + + buf->ptr[buf->size] = 0; + + return 0; + +on_error: + buf->size = orig_size; + buf->ptr[buf->size] = '\0'; + + giterr_set(GITERR_INVALID, "invalid base85 input"); + return -1; +} + int git_buf_vprintf(git_buf *buf, const char *format, va_list ap) { size_t expected_size, new_size; @@ -766,3 +857,144 @@ int git_buf_splice( buf->ptr[buf->size] = '\0'; return 0; } + +/* Quote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_buf_quote(git_buf *buf) +{ + const char whitespace[] = { 'a', 'b', 't', 'n', 'v', 'f', 'r' }; + git_buf quoted = GIT_BUF_INIT; + size_t i = 0; + bool quote = false; + int error = 0; + + /* walk to the first char that needs quoting */ + if (buf->size && buf->ptr[0] == '!') + quote = true; + + for (i = 0; !quote && i < buf->size; i++) { + if (buf->ptr[i] == '"' || buf->ptr[i] == '\\' || + buf->ptr[i] < ' ' || buf->ptr[i] > '~') { + quote = true; + break; + } + } + + if (!quote) + goto done; + + git_buf_putc("ed, '"'); + git_buf_put("ed, buf->ptr, i); + + for (; i < buf->size; i++) { + /* whitespace - use the map above, which is ordered by ascii value */ + if (buf->ptr[i] >= '\a' && buf->ptr[i] <= '\r') { + git_buf_putc("ed, '\\'); + git_buf_putc("ed, whitespace[buf->ptr[i] - '\a']); + } + + /* double quote and backslash must be escaped */ + else if (buf->ptr[i] == '"' || buf->ptr[i] == '\\') { + git_buf_putc("ed, '\\'); + git_buf_putc("ed, buf->ptr[i]); + } + + /* escape anything unprintable as octal */ + else if (buf->ptr[i] != ' ' && + (buf->ptr[i] < '!' || buf->ptr[i] > '~')) { + git_buf_printf("ed, "\\%03o", (unsigned char)buf->ptr[i]); + } + + /* yay, printable! */ + else { + git_buf_putc("ed, buf->ptr[i]); + } + } + + git_buf_putc("ed, '"'); + + if (git_buf_oom("ed)) { + error = -1; + goto done; + } + + git_buf_swap("ed, buf); + +done: + git_buf_free("ed); + return error; +} + +/* Unquote per http://marc.info/?l=git&m=112927316408690&w=2 */ +int git_buf_unquote(git_buf *buf) +{ + size_t i, j; + char ch; + + git_buf_rtrim(buf); + + if (buf->size < 2 || buf->ptr[0] != '"' || buf->ptr[buf->size-1] != '"') + goto invalid; + + for (i = 0, j = 1; j < buf->size-1; i++, j++) { + ch = buf->ptr[j]; + + if (ch == '\\') { + if (j == buf->size-2) + goto invalid; + + ch = buf->ptr[++j]; + + switch (ch) { + /* \" or \\ simply copy the char in */ + case '"': case '\\': + break; + + /* add the appropriate escaped char */ + case 'a': ch = '\a'; break; + case 'b': ch = '\b'; break; + case 'f': ch = '\f'; break; + case 'n': ch = '\n'; break; + case 'r': ch = '\r'; break; + case 't': ch = '\t'; break; + case 'v': ch = '\v'; break; + + /* \xyz digits convert to the char*/ + case '0': case '1': case '2': case '3': + if (j == buf->size-3) { + giterr_set(GITERR_INVALID, + "Truncated quoted character \\%c", ch); + return -1; + } + + if (buf->ptr[j+1] < '0' || buf->ptr[j+1] > '7' || + buf->ptr[j+2] < '0' || buf->ptr[j+2] > '7') { + giterr_set(GITERR_INVALID, + "Truncated quoted character \\%c%c%c", + buf->ptr[j], buf->ptr[j+1], buf->ptr[j+2]); + return -1; + } + + ch = ((buf->ptr[j] - '0') << 6) | + ((buf->ptr[j+1] - '0') << 3) | + (buf->ptr[j+2] - '0'); + j += 2; + break; + + default: + giterr_set(GITERR_INVALID, "Invalid quoted character \\%c", ch); + return -1; + } + } + + buf->ptr[i] = ch; + } + + buf->ptr[i] = '\0'; + buf->size = i; + + return 0; + +invalid: + giterr_set(GITERR_INVALID, "Invalid quoted line"); + return -1; +} diff --git a/src/buffer.h b/src/buffer.h index e46ee5dd7..cdfca6d99 100644 --- a/src/buffer.h +++ b/src/buffer.h @@ -173,6 +173,12 @@ void git_buf_rtrim(git_buf *buf); int git_buf_cmp(const git_buf *a, const git_buf *b); +/* Quote and unquote a buffer as specified in + * http://marc.info/?l=git&m=112927316408690&w=2 + */ +int git_buf_quote(git_buf *buf); +int git_buf_unquote(git_buf *buf); + /* Write data as base64 encoded in buffer */ int git_buf_encode_base64(git_buf *buf, const char *data, size_t len); /* Decode the given bas64 and write the result to the buffer */ @@ -180,6 +186,8 @@ int git_buf_decode_base64(git_buf *buf, const char *base64, size_t len); /* Write data as "base85" encoded in buffer */ int git_buf_encode_base85(git_buf *buf, const char *data, size_t len); +/* Decode the given "base85" and write the result to the buffer */ +int git_buf_decode_base85(git_buf *buf, const char *base64, size_t len, size_t output_len); /* * Insert, remove or replace a portion of the buffer. diff --git a/src/checkout.c b/src/checkout.c index 1bea00da8..62950913e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -26,6 +26,7 @@ #include "filter.h" #include "blob.h" #include "diff.h" +#include "diff_generate.h" #include "pathspec.h" #include "buf_text.h" #include "diff_xdiff.h" @@ -66,8 +67,8 @@ typedef struct { git_vector update_conflicts; git_vector *update_reuc; git_vector *update_names; - git_buf path; - size_t workdir_len; + git_buf target_path; + size_t target_len; git_buf tmp; unsigned int strategy; int can_symlink; @@ -211,6 +212,10 @@ static bool checkout_is_workdir_modified( if (baseitem->size && wditem->file_size != baseitem->size) return true; + /* if the workdir item is a directory, it cannot be a modified file */ + if (S_ISDIR(wditem->mode)) + return false; + if (git_diff__oid_for_entry(&oid, data->diff, wditem, wditem->mode, NULL) < 0) return false; @@ -294,14 +299,30 @@ static int checkout_action_no_wd( return checkout_action_common(action, data, delta, NULL); } -static bool wd_item_is_removable(git_iterator *iter, const git_index_entry *wd) +static int checkout_target_fullpath( + git_buf **out, checkout_data *data, const char *path) { - git_buf *full = NULL; + git_buf_truncate(&data->target_path, data->target_len); + + if (path && git_buf_puts(&data->target_path, path) < 0) + return -1; + + *out = &data->target_path; + + return 0; +} + +static bool wd_item_is_removable( + checkout_data *data, const git_index_entry *wd) +{ + git_buf *full; if (wd->mode != GIT_FILEMODE_TREE) return true; - if (git_iterator_current_workdir_path(&full, iter) < 0) - return true; + + if (checkout_target_fullpath(&full, data, wd->path) < 0) + return false; + return !full || !git_path_contains(full, DOT_GIT); } @@ -363,14 +384,14 @@ static int checkout_action_wd_only( if ((error = checkout_notify(data, notify, NULL, wd)) != 0) return error; - if (remove && wd_item_is_removable(workdir, wd)) + if (remove && wd_item_is_removable(data, wd)) error = checkout_queue_remove(data, wd->path); if (!error) error = git_iterator_advance(wditem, workdir); } else { /* untracked or ignored - can't know which until we advance through */ - bool over = false, removable = wd_item_is_removable(workdir, wd); + bool over = false, removable = wd_item_is_removable(data, wd); git_iterator_status_t untracked_state; /* copy the entry for issuing notification callback later */ @@ -378,7 +399,7 @@ static int checkout_action_wd_only( git_buf_sets(&data->tmp, wd->path); saved_wd.path = data->tmp.ptr; - error = git_iterator_advance_over_with_status( + error = git_iterator_advance_over( wditem, &untracked_state, workdir); if (error == GIT_ITEROVER) over = true; @@ -428,10 +449,12 @@ static bool submodule_is_config_only( static bool checkout_is_empty_dir(checkout_data *data, const char *path) { - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, path) < 0) + git_buf *fullpath; + + if (checkout_target_fullpath(&fullpath, data, path) < 0) return false; - return git_path_is_empty_dir(data->path.ptr); + + return git_path_is_empty_dir(fullpath->ptr); } static int checkout_action_with_wd( @@ -640,7 +663,7 @@ static int checkout_action( if (cmp == 0) { if (wd->mode == GIT_FILEMODE_TREE) { /* case 2 - entry prefixed by workdir tree */ - error = git_iterator_advance_into_or_over(wditem, workdir); + error = git_iterator_advance_into(wditem, workdir); if (error < 0 && error != GIT_ITEROVER) goto done; continue; @@ -913,7 +936,7 @@ static int checkout_conflicts_load(checkout_data *data, git_iterator *workdir, g git_index *index; /* Only write conficts from sources that have them: indexes. */ - if ((index = git_iterator_get_index(data->target)) == NULL) + if ((index = git_iterator_index(data->target)) == NULL) return 0; data->update_conflicts._cmp = checkout_conflictdata_cmp; @@ -1063,7 +1086,7 @@ static int checkout_conflicts_coalesce_renames( size_t i, names; int error = 0; - if ((index = git_iterator_get_index(data->target)) == NULL) + if ((index = git_iterator_index(data->target)) == NULL) return 0; /* Juggle entries based on renames */ @@ -1121,7 +1144,7 @@ static int checkout_conflicts_mark_directoryfile( const char *path; int prefixed, error = 0; - if ((index = git_iterator_get_index(data->target)) == NULL) + if ((index = git_iterator_index(data->target)) == NULL) return 0; len = git_index_entrycount(index); @@ -1585,18 +1608,18 @@ static int checkout_submodule_update_index( checkout_data *data, const git_diff_file *file) { + git_buf *fullpath; struct stat st; /* update the index unless prevented */ if ((data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) != 0) return 0; - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, file->path) < 0) + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) return -1; data->perfdata.stat_calls++; - if (p_stat(git_buf_cstr(&data->path), &st) < 0) { + if (p_stat(fullpath->ptr, &st) < 0) { giterr_set( GITERR_CHECKOUT, "Could not stat submodule %s\n", file->path); return GIT_ENOTFOUND; @@ -1721,22 +1744,23 @@ static int checkout_blob( checkout_data *data, const git_diff_file *file) { - int error = 0; + git_buf *fullpath; struct stat st; + int error = 0; - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, file->path) < 0) + if (checkout_target_fullpath(&fullpath, data, file->path) < 0) return -1; if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0) { int rval = checkout_safe_for_update_only( - data, git_buf_cstr(&data->path), file->mode); + data, fullpath->ptr, file->mode); + if (rval <= 0) return rval; } error = checkout_write_content( - data, &file->id, git_buf_cstr(&data->path), NULL, file->mode, &st); + data, &file->id, fullpath->ptr, NULL, file->mode, &st); /* update the index unless prevented */ if (!error && (data->strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0) @@ -1757,18 +1781,21 @@ static int checkout_remove_the_old( git_diff_delta *delta; const char *str; size_t i; - const char *workdir = git_buf_cstr(&data->path); + git_buf *fullpath; uint32_t flg = GIT_RMDIR_EMPTY_PARENTS | GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS; if (data->opts.checkout_strategy & GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES) flg |= GIT_RMDIR_SKIP_NONEMPTY; - git_buf_truncate(&data->path, data->workdir_len); + if (checkout_target_fullpath(&fullpath, data, NULL) < 0) + return -1; git_vector_foreach(&data->diff->deltas, i, delta) { if (actions[i] & CHECKOUT_ACTION__REMOVE) { - error = git_futils_rmdir_r(delta->old_file.path, workdir, flg); + error = git_futils_rmdir_r( + delta->old_file.path, fullpath->ptr, flg); + if (error < 0) return error; @@ -1785,7 +1812,7 @@ static int checkout_remove_the_old( } git_vector_foreach(&data->removes, i, str) { - error = git_futils_rmdir_r(str, workdir, flg); + error = git_futils_rmdir_r(str, fullpath->ptr, flg); if (error < 0) return error; @@ -1939,7 +1966,7 @@ static int checkout_path_suffixed(git_buf *path, const char *suffix) if (i == INT_MAX) { git_buf_truncate(path, path_len); - giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path); + giterr_set(GITERR_CHECKOUT, "Could not write '%s': working directory file exists", path->ptr); return GIT_EEXISTS; } @@ -1952,13 +1979,13 @@ static int checkout_write_entry( const git_index_entry *side) { const char *hint_path = NULL, *suffix; + git_buf *fullpath; struct stat st; int error; assert (side == conflict->ours || side == conflict->theirs); - git_buf_truncate(&data->path, data->workdir_len); - if (git_buf_puts(&data->path, side->path) < 0) + if (checkout_target_fullpath(&fullpath, data, side->path) < 0) return -1; if ((conflict->name_collision || conflict->directoryfile) && @@ -1972,18 +1999,18 @@ static int checkout_write_entry( suffix = data->opts.their_label ? data->opts.their_label : "theirs"; - if (checkout_path_suffixed(&data->path, suffix) < 0) + if (checkout_path_suffixed(fullpath, suffix) < 0) return -1; hint_path = side->path; } if ((data->strategy & GIT_CHECKOUT_UPDATE_ONLY) != 0 && - (error = checkout_safe_for_update_only(data, git_buf_cstr(&data->path), side->mode)) <= 0) + (error = checkout_safe_for_update_only(data, fullpath->ptr, side->mode)) <= 0) return error; return checkout_write_content(data, - &side->id, git_buf_cstr(&data->path), hint_path, side->mode, &st); + &side->id, fullpath->ptr, hint_path, side->mode, &st); } static int checkout_write_entries( @@ -2296,7 +2323,7 @@ static void checkout_data_clear(checkout_data *data) git_strmap_free(data->mkdir_map); - git_buf_free(&data->path); + git_buf_free(&data->target_path); git_buf_free(&data->tmp); git_index_free(data->index); @@ -2359,7 +2386,7 @@ static int checkout_data_init( if ((error = git_repository_index(&data->index, data->repo)) < 0) goto cleanup; - if (data->index != git_iterator_get_index(target)) { + if (data->index != git_iterator_index(target)) { if ((error = git_index_read(data->index, true)) < 0) goto cleanup; @@ -2442,7 +2469,7 @@ static int checkout_data_init( data->opts.checkout_strategy |= GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; else { giterr_set(GITERR_CHECKOUT, "unknown style '%s' given for 'merge.conflictstyle'", - conflict_style); + conflict_style->value); error = -1; git_config_entry_free(conflict_style); goto cleanup; @@ -2455,12 +2482,12 @@ static int checkout_data_init( if ((error = git_vector_init(&data->removes, 0, git__strcmp_cb)) < 0 || (error = git_vector_init(&data->remove_conflicts, 0, NULL)) < 0 || (error = git_vector_init(&data->update_conflicts, 0, NULL)) < 0 || - (error = git_buf_puts(&data->path, data->opts.target_directory)) < 0 || - (error = git_path_to_dir(&data->path)) < 0 || + (error = git_buf_puts(&data->target_path, data->opts.target_directory)) < 0 || + (error = git_path_to_dir(&data->target_path)) < 0 || (error = git_strmap_alloc(&data->mkdir_map)) < 0) goto cleanup; - data->workdir_len = git_buf_len(&data->path); + data->target_len = git_buf_len(&data->target_path); git_attr_session__init(&data->attr_session, data->repo); @@ -2516,7 +2543,7 @@ int git_checkout_iterator( workdir_opts.start = data.pfx; workdir_opts.end = data.pfx; - if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || + if ((error = git_iterator_reset_range(target, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_workdir_ext( &workdir, data.repo, data.opts.target_directory, index, NULL, &workdir_opts)) < 0) @@ -2586,7 +2613,7 @@ int git_checkout_iterator( (error = checkout_create_conflicts(&data)) < 0) goto cleanup; - if (data.index != git_iterator_get_index(target) && + if (data.index != git_iterator_index(target) && (error = checkout_extensions_update_index(&data)) < 0) goto cleanup; diff --git a/src/clone.c b/src/clone.c index 6b4b7ae53..0d4756e28 100644 --- a/src/clone.c +++ b/src/clone.c @@ -461,14 +461,6 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version) return 0; } -static const char *repository_base(git_repository *repo) -{ - if (git_repository_is_bare(repo)) - return git_repository_path(repo); - - return git_repository_workdir(repo); -} - static bool can_link(const char *src, const char *dst, int link) { #ifdef GIT_WIN32 diff --git a/src/commit.c b/src/commit.c index ade4e10b5..76e6dcbc9 100644 --- a/src/commit.c +++ b/src/commit.c @@ -18,6 +18,7 @@ #include "message.h" #include "refs.h" #include "object.h" +#include "oidarray.h" void git_commit__free(void *_commit) { @@ -37,6 +38,84 @@ void git_commit__free(void *_commit) git__free(commit); } +static int git_commit__create_buffer_internal( + git_buf *out, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_oid *tree, + git_array_oid_t *parents) +{ + size_t i = 0; + const git_oid *parent; + + assert(out && tree); + + git_oid__writebuf(out, "tree ", tree); + + for (i = 0; i < git_array_size(*parents); i++) { + parent = git_array_get(*parents, i); + git_oid__writebuf(out, "parent ", parent); + } + + git_signature__writebuf(out, "author ", author); + git_signature__writebuf(out, "committer ", committer); + + if (message_encoding != NULL) + git_buf_printf(out, "encoding %s\n", message_encoding); + + git_buf_putc(out, '\n'); + + if (git_buf_puts(out, message) < 0) + goto on_error; + + return 0; + +on_error: + git_buf_free(out); + return -1; +} + +static int validate_tree_and_parents(git_array_oid_t *parents, git_repository *repo, const git_oid *tree, + git_commit_parent_callback parent_cb, void *parent_payload, + const git_oid *current_id, bool validate) +{ + size_t i; + int error; + git_oid *parent_cpy; + const git_oid *parent; + + if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE)) + return -1; + + i = 0; + while ((parent = parent_cb(i, parent_payload)) != NULL) { + if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) { + error = -1; + goto on_error; + } + + parent_cpy = git_array_alloc(*parents); + GITERR_CHECK_ALLOC(parent_cpy); + + git_oid_cpy(parent_cpy, parent); + i++; + } + + if (current_id && (parents->size == 0 || git_oid_cmp(current_id, git_array_get(*parents, 0)))) { + giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent"); + error = GIT_EMODIFIED; + goto on_error; + } + + return 0; + +on_error: + git_array_clear(*parents); + return error; +} + static int git_commit__create_internal( git_oid *id, git_repository *repo, @@ -50,18 +129,12 @@ static int git_commit__create_internal( void *parent_payload, bool validate) { - git_reference *ref = NULL; - int error = 0, matched_parent = 0; - const git_oid *current_id = NULL; - git_buf commit = GIT_BUF_INIT; - size_t i = 0; + int error; git_odb *odb; - const git_oid *parent; - - assert(id && repo && tree && parent_cb); - - if (validate && !git_object__is_valid(repo, tree, GIT_OBJ_TREE)) - return -1; + git_reference *ref = NULL; + git_buf buf = GIT_BUF_INIT; + const git_oid *current_id = NULL; + git_array_oid_t parents = GIT_ARRAY_INIT; if (update_ref) { error = git_reference_lookup_resolved(&ref, repo, update_ref, 10); @@ -73,58 +146,34 @@ static int git_commit__create_internal( if (ref) current_id = git_reference_target(ref); - git_oid__writebuf(&commit, "tree ", tree); + if ((error = validate_tree_and_parents(&parents, repo, tree, parent_cb, parent_payload, current_id, validate)) < 0) + goto cleanup; - while ((parent = parent_cb(i, parent_payload)) != NULL) { - if (validate && !git_object__is_valid(repo, parent, GIT_OBJ_COMMIT)) { - error = -1; - goto on_error; - } + error = git_commit__create_buffer_internal(&buf, author, committer, + message_encoding, message, tree, + &parents); - git_oid__writebuf(&commit, "parent ", parent); - if (i == 0 && current_id && git_oid_equal(current_id, parent)) - matched_parent = 1; - i++; - } - - if (ref && !matched_parent) { - git_reference_free(ref); - git_buf_free(&commit); - giterr_set(GITERR_OBJECT, "failed to create commit: current tip is not the first parent"); - return GIT_EMODIFIED; - } - - git_signature__writebuf(&commit, "author ", author); - git_signature__writebuf(&commit, "committer ", committer); - - if (message_encoding != NULL) - git_buf_printf(&commit, "encoding %s\n", message_encoding); - - git_buf_putc(&commit, '\n'); - - if (git_buf_puts(&commit, message) < 0) - goto on_error; + if (error < 0) + goto cleanup; if (git_repository_odb__weakptr(&odb, repo) < 0) - goto on_error; + goto cleanup; - if (git_odb_write(id, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT) < 0) - goto on_error; + if (git_odb_write(id, odb, buf.ptr, buf.size, GIT_OBJ_COMMIT) < 0) + goto cleanup; - git_buf_free(&commit); if (update_ref != NULL) { error = git_reference__update_for_commit( repo, ref, update_ref, id, "commit"); - git_reference_free(ref); - return error; + goto cleanup; } - return 0; - -on_error: - git_buf_free(&commit); - return -1; +cleanup: + git_array_clear(parents); + git_reference_free(ref); + git_buf_free(&buf); + return error; } int git_commit_create_from_callback( @@ -566,7 +615,7 @@ int git_commit_nth_gen_ancestor( assert(ancestor && commit); - if (git_object_dup((git_object **) ¤t, (git_object *) commit) < 0) + if (git_commit_dup(¤t, (git_commit *)commit) < 0) return -1; if (n == 0) { @@ -740,3 +789,98 @@ cleanup: git_buf_clear(signed_data); return error; } + +int git_commit_create_buffer(git_buf *out, + git_repository *repo, + const git_signature *author, + const git_signature *committer, + const char *message_encoding, + const char *message, + const git_tree *tree, + size_t parent_count, + const git_commit *parents[]) +{ + int error; + commit_parent_data data = { parent_count, parents, repo }; + git_array_oid_t parents_arr = GIT_ARRAY_INIT; + const git_oid *tree_id; + + assert(tree && git_tree_owner(tree) == repo); + + tree_id = git_tree_id(tree); + + if ((error = validate_tree_and_parents(&parents_arr, repo, tree_id, commit_parent_from_array, &data, NULL, true)) < 0) + return error; + + error = git_commit__create_buffer_internal( + out, author, committer, + message_encoding, message, tree_id, + &parents_arr); + + git_array_clear(parents_arr); + return error; +} + +/** + * Append to 'out' properly marking continuations when there's a newline in 'content' + */ +static void format_header_field(git_buf *out, const char *field, const char *content) +{ + const char *lf; + + assert(out && field && content); + + git_buf_puts(out, field); + git_buf_putc(out, ' '); + + while ((lf = strchr(content, '\n')) != NULL) { + git_buf_put(out, content, lf - content); + git_buf_puts(out, "\n "); + content = lf + 1; + } + + git_buf_puts(out, content); + git_buf_putc(out, '\n'); +} + +int git_commit_create_with_signature( + git_oid *out, + git_repository *repo, + const char *commit_content, + const char *signature, + const char *signature_field) +{ + git_odb *odb; + int error = 0; + const char *field; + const char *header_end; + git_buf commit = GIT_BUF_INIT; + + /* We start by identifying the end of the commit header */ + header_end = strstr(commit_content, "\n\n"); + if (!header_end) { + giterr_set(GITERR_INVALID, "malformed commit contents"); + return -1; + } + + field = signature_field ? signature_field : "gpgsig"; + + /* The header ends after the first LF */ + header_end++; + git_buf_put(&commit, commit_content, header_end - commit_content); + format_header_field(&commit, field, signature); + git_buf_puts(&commit, header_end); + + if (git_buf_oom(&commit)) + return -1; + + if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) + goto cleanup; + + if ((error = git_odb_write(out, odb, commit.ptr, commit.size, GIT_OBJ_COMMIT)) < 0) + goto cleanup; + +cleanup: + git_buf_free(&commit); + return error; +} diff --git a/src/commit_list.c b/src/commit_list.c index 28948c88b..a1681ffae 100644 --- a/src/commit_list.c +++ b/src/commit_list.c @@ -13,10 +13,15 @@ int git_commit_list_time_cmp(const void *a, const void *b) { - const git_commit_list_node *commit_a = a; - const git_commit_list_node *commit_b = b; + int64_t time_a = ((git_commit_list_node *) a)->time; + int64_t time_b = ((git_commit_list_node *) b)->time; - return (commit_a->time < commit_b->time); + if (time_a < time_b) + return 1; + if (time_a > time_b) + return -1; + + return 0; } git_commit_list *git_commit_list_insert(git_commit_list_node *item, git_commit_list **list_p) diff --git a/src/commit_list.h b/src/commit_list.h index a6967bcef..9746c2801 100644 --- a/src/commit_list.h +++ b/src/commit_list.h @@ -28,6 +28,7 @@ typedef struct git_commit_list_node { uninteresting:1, topo_delay:1, parsed:1, + added:1, flags : FLAG_BITS; unsigned short in_degree; diff --git a/src/common.h b/src/common.h index 51fb9186e..f12cc98cf 100644 --- a/src/common.h +++ b/src/common.h @@ -103,7 +103,8 @@ /** * Set the error message for this thread, formatting as needed. */ -void giterr_set(int error_class, const char *string, ...); + +void giterr_set(int error_class, const char *string, ...) GIT_FORMAT_PRINTF(2, 3); /** * Set the error message for a regex failure, using the internal regex diff --git a/src/config.c b/src/config.c index f4d4cb2b9..403b7090d 100644 --- a/src/config.c +++ b/src/config.c @@ -478,7 +478,7 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf iter = git__calloc(1, sizeof(all_iter)); GITERR_CHECK_ALLOC(iter); - if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) != 0) { + if ((result = p_regcomp(&iter->regex, regexp, REG_EXTENDED)) != 0) { giterr_set_regex(&iter->regex, result); git__free(iter); return -1; @@ -512,7 +512,7 @@ int git_config_backend_foreach_match( int error = 0; if (regexp != NULL) { - if ((error = regcomp(®ex, regexp, REG_EXTENDED)) != 0) { + if ((error = p_regcomp(®ex, regexp, REG_EXTENDED)) != 0) { giterr_set_regex(®ex, error); regfree(®ex); return -1; @@ -1003,7 +1003,7 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config goto on_error; if (regexp != NULL) { - error = regcomp(&iter->regex, regexp, REG_EXTENDED); + error = p_regcomp(&iter->regex, regexp, REG_EXTENDED); if (error != 0) { giterr_set_regex(&iter->regex, error); error = -1; diff --git a/src/config_file.c b/src/config_file.c index ca4345cc7..9ff021e7e 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -232,7 +232,10 @@ static refcounted_strmap *refcounted_strmap_take(diskfile_header *h) { refcounted_strmap *map; - git_mutex_lock(&h->values_mutex); + if (git_mutex_lock(&h->values_mutex) < 0) { + giterr_set(GITERR_OS, "Failed to lock config backend"); + return NULL; + } map = h->values; git_atomic_inc(&map->refcount); @@ -318,7 +321,10 @@ static int config__refresh(git_config_backend *cfg) if ((error = config_read(values->values, b, reader, b->level, 0)) < 0) goto out; - git_mutex_lock(&b->header.values_mutex); + if ((error = git_mutex_lock(&b->header.values_mutex)) < 0) { + giterr_set(GITERR_OS, "Failed to lock config backend"); + goto out; + } tmp = b->header.values; b->header.values = values; @@ -460,7 +466,8 @@ static int config_set(git_config_backend *cfg, const char *name, const char *val if ((rval = git_config__normalize_name(name, &key)) < 0) return rval; - map = refcounted_strmap_take(&b->header); + if ((map = refcounted_strmap_take(&b->header)) == NULL) + return -1; values = map->values; /* @@ -527,7 +534,8 @@ static int config_get(git_config_backend *cfg, const char *key, git_config_entry if (!h->parent.readonly && ((error = config_refresh(cfg)) < 0)) return error; - map = refcounted_strmap_take(h); + if ((map = refcounted_strmap_take(h)) == NULL) + return -1; values = map->values; pos = git_strmap_lookup_index(values, key); @@ -562,7 +570,7 @@ static int config_set_multivar( if ((result = git_config__normalize_name(name, &key)) < 0) return result; - result = regcomp(&preg, regexp, REG_EXTENDED); + result = p_regcomp(&preg, regexp, REG_EXTENDED); if (result != 0) { giterr_set_regex(&preg, result); result = -1; @@ -594,7 +602,8 @@ static int config_delete(git_config_backend *cfg, const char *name) if ((result = git_config__normalize_name(name, &key)) < 0) return result; - map = refcounted_strmap_take(&b->header); + if ((map = refcounted_strmap_take(&b->header)) == NULL) + return -1; values = b->header.values->values; pos = git_strmap_lookup_index(values, key); @@ -633,7 +642,8 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con if ((result = git_config__normalize_name(name, &key)) < 0) return result; - map = refcounted_strmap_take(&b->header); + if ((map = refcounted_strmap_take(&b->header)) == NULL) + return -1; values = b->header.values->values; pos = git_strmap_lookup_index(values, key); @@ -647,7 +657,7 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con refcounted_strmap_free(map); - result = regcomp(&preg, regexp, REG_EXTENDED); + result = p_regcomp(&preg, regexp, REG_EXTENDED); if (result != 0) { giterr_set_regex(&preg, result); result = -1; @@ -816,7 +826,8 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve /* We're just copying data, don't care about the level */ GIT_UNUSED(level); - src_map = refcounted_strmap_take(src_header); + if ((src_map = refcounted_strmap_take(src_header)) == NULL) + return -1; b->header.values = src_map; return 0; @@ -1946,4 +1957,3 @@ done: git_buf_free(&reader->buffer); return result; } - diff --git a/src/crlf.c b/src/crlf.c index 5d7510ac7..11895b19f 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -289,6 +289,7 @@ static int crlf_check( ca.eol = check_eol(attr_values[1]); /* eol */ } ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT; + ca.safe_crlf = GIT_SAFE_CRLF_DEFAULT; /* * Use the core Git logic to see if we should perform CRLF for this file diff --git a/src/curl_stream.c b/src/curl_stream.c index 9963d94cc..4e0455cca 100644 --- a/src/curl_stream.c +++ b/src/curl_stream.c @@ -13,6 +13,17 @@ #include "git2/transport.h" #include "buffer.h" #include "vector.h" +#include "proxy.h" + +/* This is for backwards compatibility with curl<7.45.0. */ +#ifndef CURLINFO_ACTIVESOCKET +# define CURLINFO_ACTIVESOCKET CURLINFO_LASTSOCKET +# define GIT_CURL_BADSOCKET -1 +# define git_activesocket_t long +#else +# define GIT_CURL_BADSOCKET CURL_SOCKET_BAD +# define git_activesocket_t curl_socket_t +#endif typedef struct { git_stream parent; @@ -21,6 +32,8 @@ typedef struct { char curl_error[CURL_ERROR_SIZE + 1]; git_cert_x509 cert_info; git_strarray cert_info_strings; + git_proxy_options proxy; + git_cred *proxy_cred; } curl_stream; static int seterr_curl(curl_stream *s) @@ -29,21 +42,100 @@ static int seterr_curl(curl_stream *s) return -1; } +GIT_INLINE(int) error_no_credentials(void) +{ + giterr_set(GITERR_NET, "proxy authentication required, but no callback provided"); + return GIT_EAUTH; +} + +static int apply_proxy_creds(curl_stream *s) +{ + CURLcode res; + git_cred_userpass_plaintext *userpass; + + if (!s->proxy_cred) + return GIT_ENOTFOUND; + + userpass = (git_cred_userpass_plaintext *) s->proxy_cred; + if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYUSERNAME, userpass->username)) != CURLE_OK) + return seterr_curl(s); + if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYPASSWORD, userpass->password)) != CURLE_OK) + return seterr_curl(s); + + return 0; +} + +static int ask_and_apply_proxy_creds(curl_stream *s) +{ + int error; + git_proxy_options *opts = &s->proxy; + + if (!opts->credentials) + return error_no_credentials(); + + /* TODO: see if PROXYAUTH_AVAIL helps us here */ + git_cred_free(s->proxy_cred); + s->proxy_cred = NULL; + giterr_clear(); + error = opts->credentials(&s->proxy_cred, opts->url, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, opts->payload); + if (error == GIT_PASSTHROUGH) + return error_no_credentials(); + if (error < 0) { + if (!giterr_last()) + giterr_set(GITERR_NET, "proxy authentication was aborted by the user"); + return error; + } + + if (s->proxy_cred->credtype != GIT_CREDTYPE_USERPASS_PLAINTEXT) { + giterr_set(GITERR_NET, "credentials callback returned invalid credential type"); + return -1; + } + + return apply_proxy_creds(s); +} + static int curls_connect(git_stream *stream) { curl_stream *s = (curl_stream *) stream; - long sockextr; - int failed_cert = 0; + git_activesocket_t sockextr; + long connect_last = 0; + int failed_cert = 0, error; + bool retry_connect; CURLcode res; - res = curl_easy_perform(s->handle); + + /* Apply any credentials we've already established */ + error = apply_proxy_creds(s); + if (error < 0 && error != GIT_ENOTFOUND) + return seterr_curl(s); + + do { + retry_connect = 0; + res = curl_easy_perform(s->handle); + + curl_easy_getinfo(s->handle, CURLINFO_HTTP_CONNECTCODE, &connect_last); + + /* HTTP 407 Proxy Authentication Required */ + if (connect_last == 407) { + if ((error = ask_and_apply_proxy_creds(s)) < 0) + return error; + + retry_connect = true; + } + } while (retry_connect); if (res != CURLE_OK && res != CURLE_PEER_FAILED_VERIFICATION) return seterr_curl(s); if (res == CURLE_PEER_FAILED_VERIFICATION) failed_cert = 1; - if ((res = curl_easy_getinfo(s->handle, CURLINFO_LASTSOCKET, &sockextr)) != CURLE_OK) + if ((res = curl_easy_getinfo(s->handle, CURLINFO_ACTIVESOCKET, &sockextr)) != CURLE_OK) { return seterr_curl(s); + } + + if (sockextr == GIT_CURL_BADSOCKET) { + giterr_set(GITERR_NET, "curl socket is no longer valid"); + return -1; + } s->socket = sockextr; @@ -95,12 +187,19 @@ static int curls_certificate(git_cert **out, git_stream *stream) return 0; } -static int curls_set_proxy(git_stream *stream, const char *proxy_url) +static int curls_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) { + int error; CURLcode res; curl_stream *s = (curl_stream *) stream; - if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, proxy_url)) != CURLE_OK) + if ((error = git_proxy_options_dup(&s->proxy, proxy_opts)) < 0) + return error; + + if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXY, s->proxy.url)) != CURLE_OK) + return seterr_curl(s); + + if ((res = curl_easy_setopt(s->handle, CURLOPT_PROXYAUTH, CURLAUTH_ANY)) != CURLE_OK) return seterr_curl(s); return 0; @@ -115,6 +214,7 @@ static int wait_for(curl_socket_t fd, bool reading) FD_ZERO(&outfd); FD_ZERO(&errfd); + assert(fd >= 0); FD_SET(fd, &errfd); if (reading) FD_SET(fd, &infd); diff --git a/src/delta-apply.c b/src/delta-apply.c deleted file mode 100644 index 02ec7b75e..000000000 --- a/src/delta-apply.c +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#include "common.h" -#include "git2/odb.h" -#include "delta-apply.h" - -/* - * This file was heavily cribbed from BinaryDelta.java in JGit, which - * itself was heavily cribbed from patch-delta.c in the - * GIT project. The original delta patching code was written by - * Nicolas Pitre . - */ - -static int hdr_sz( - size_t *size, - const unsigned char **delta, - const unsigned char *end) -{ - const unsigned char *d = *delta; - size_t r = 0; - unsigned int c, shift = 0; - - do { - if (d == end) - return -1; - c = *d++; - r |= (c & 0x7f) << shift; - shift += 7; - } while (c & 0x80); - *delta = d; - *size = r; - return 0; -} - -int git__delta_read_header( - const unsigned char *delta, - size_t delta_len, - size_t *base_sz, - size_t *res_sz) -{ - const unsigned char *delta_end = delta + delta_len; - if ((hdr_sz(base_sz, &delta, delta_end) < 0) || - (hdr_sz(res_sz, &delta, delta_end) < 0)) - return -1; - return 0; -} - -#define DELTA_HEADER_BUFFER_LEN 16 -int git__delta_read_header_fromstream(size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) -{ - static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; - unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; - const unsigned char *delta, *delta_end; - size_t len; - ssize_t read; - - len = read = 0; - while (len < buffer_len) { - read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); - - if (read == 0) - break; - - if (read == GIT_EBUFS) - continue; - - len += read; - } - - delta = buffer; - delta_end = delta + len; - if ((hdr_sz(base_sz, &delta, delta_end) < 0) || - (hdr_sz(res_sz, &delta, delta_end) < 0)) - return -1; - - return 0; -} - -int git__delta_apply( - git_rawobj *out, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len) -{ - const unsigned char *delta_end = delta + delta_len; - size_t base_sz, res_sz, alloc_sz; - unsigned char *res_dp; - - /* Check that the base size matches the data we were given; - * if not we would underflow while accessing data from the - * base object, resulting in data corruption or segfault. - */ - if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { - giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); - return -1; - } - - if (hdr_sz(&res_sz, &delta, delta_end) < 0) { - giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); - return -1; - } - - GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); - res_dp = git__malloc(alloc_sz); - GITERR_CHECK_ALLOC(res_dp); - - res_dp[res_sz] = '\0'; - out->data = res_dp; - out->len = res_sz; - - while (delta < delta_end) { - unsigned char cmd = *delta++; - if (cmd & 0x80) { - /* cmd is a copy instruction; copy from the base. - */ - size_t off = 0, len = 0; - - if (cmd & 0x01) off = *delta++; - if (cmd & 0x02) off |= *delta++ << 8UL; - if (cmd & 0x04) off |= *delta++ << 16UL; - if (cmd & 0x08) off |= *delta++ << 24UL; - - if (cmd & 0x10) len = *delta++; - if (cmd & 0x20) len |= *delta++ << 8UL; - if (cmd & 0x40) len |= *delta++ << 16UL; - if (!len) len = 0x10000; - - if (base_len < off + len || res_sz < len) - goto fail; - memcpy(res_dp, base + off, len); - res_dp += len; - res_sz -= len; - - } else if (cmd) { - /* cmd is a literal insert instruction; copy from - * the delta stream itself. - */ - if (delta_end - delta < cmd || res_sz < cmd) - goto fail; - memcpy(res_dp, delta, cmd); - delta += cmd; - res_dp += cmd; - res_sz -= cmd; - - } else { - /* cmd == 0 is reserved for future encodings. - */ - goto fail; - } - } - - if (delta != delta_end || res_sz) - goto fail; - return 0; - -fail: - git__free(out->data); - out->data = NULL; - giterr_set(GITERR_INVALID, "Failed to apply delta"); - return -1; -} diff --git a/src/delta-apply.h b/src/delta-apply.h deleted file mode 100644 index eeeb78682..000000000 --- a/src/delta-apply.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_delta_apply_h__ -#define INCLUDE_delta_apply_h__ - -#include "odb.h" -#include "pack.h" - -/** - * Apply a git binary delta to recover the original content. - * - * @param out the output buffer to receive the original data. - * Only out->data and out->len are populated, as this is - * the only information available in the delta. - * @param base the base to copy from during copy instructions. - * @param base_len number of bytes available at base. - * @param delta the delta to execute copy/insert instructions from. - * @param delta_len total number of bytes in the delta. - * @return - * - 0 on a successful delta unpack. - * - GIT_ERROR if the delta is corrupt or doesn't match the base. - */ -extern int git__delta_apply( - git_rawobj *out, - const unsigned char *base, - size_t base_len, - const unsigned char *delta, - size_t delta_len); - -/** - * Read the header of a git binary delta. - * - * @param delta the delta to execute copy/insert instructions from. - * @param delta_len total number of bytes in the delta. - * @param base_sz pointer to store the base size field. - * @param res_sz pointer to store the result size field. - * @return - * - 0 on a successful decoding the header. - * - GIT_ERROR if the delta is corrupt. - */ -extern int git__delta_read_header( - const unsigned char *delta, - size_t delta_len, - size_t *base_sz, - size_t *res_sz); - -/** - * Read the header of a git binary delta - * - * This variant reads just enough from the packfile stream to read the - * delta header. - */ -extern int git__delta_read_header_fromstream( - size_t *base_sz, - size_t *res_sz, - git_packfile_stream *stream); - -#endif diff --git a/src/delta.c b/src/delta.c index d72d820d8..dc45697b6 100644 --- a/src/delta.c +++ b/src/delta.c @@ -114,7 +114,7 @@ struct index_entry { struct git_delta_index { unsigned long memsize; const void *src_buf; - unsigned long src_size; + size_t src_size; unsigned int hash_mask; struct index_entry *hash[GIT_FLEX_ARRAY]; }; @@ -142,8 +142,8 @@ static int lookup_index_alloc( return 0; } -struct git_delta_index * -git_delta_create_index(const void *buf, unsigned long bufsize) +int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize) { unsigned int i, hsize, hmask, entries, prev_val, *hash_count; const unsigned char *data, *buffer = buf; @@ -152,8 +152,10 @@ git_delta_create_index(const void *buf, unsigned long bufsize) void *mem; unsigned long memsize; + *out = NULL; + if (!buf || !bufsize) - return NULL; + return 0; /* Determine index hash size. Note that indexing skips the first byte to allow for optimizing the rabin polynomial @@ -172,7 +174,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize) hmask = hsize - 1; if (lookup_index_alloc(&mem, &memsize, entries, hsize) < 0) - return NULL; + return -1; index = mem; mem = index->hash; @@ -190,7 +192,7 @@ git_delta_create_index(const void *buf, unsigned long bufsize) hash_count = git__calloc(hsize, sizeof(*hash_count)); if (!hash_count) { git__free(index); - return NULL; + return -1; } /* then populate the index */ @@ -243,20 +245,20 @@ git_delta_create_index(const void *buf, unsigned long bufsize) } git__free(hash_count); - return index; + *out = index; + return 0; } -void git_delta_free_index(struct git_delta_index *index) +void git_delta_index_free(git_delta_index *index) { git__free(index); } -unsigned long git_delta_sizeof_index(struct git_delta_index *index) +size_t git_delta_index_size(git_delta_index *index) { - if (index) - return index->memsize; - else - return 0; + assert(index); + + return index->memsize; } /* @@ -265,55 +267,57 @@ unsigned long git_delta_sizeof_index(struct git_delta_index *index) */ #define MAX_OP_SIZE (5 + 5 + 1 + RABIN_WINDOW + 7) -void * -git_delta_create( +int git_delta_create_from_index( + void **out, + size_t *out_len, const struct git_delta_index *index, const void *trg_buf, - unsigned long trg_size, - unsigned long *delta_size, - unsigned long max_size) + size_t trg_size, + size_t max_size) { - unsigned int i, outpos, outsize, moff, msize, val; + unsigned int i, bufpos, bufsize, moff, msize, val; int inscnt; const unsigned char *ref_data, *ref_top, *data, *top; - unsigned char *out; + unsigned char *buf; + + *out = NULL; + *out_len = 0; if (!trg_buf || !trg_size) - return NULL; + return 0; - outpos = 0; - outsize = 8192; - if (max_size && outsize >= max_size) - outsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); - out = git__malloc(outsize); - if (!out) - return NULL; + bufpos = 0; + bufsize = 8192; + if (max_size && bufsize >= max_size) + bufsize = (unsigned int)(max_size + MAX_OP_SIZE + 1); + buf = git__malloc(bufsize); + GITERR_CHECK_ALLOC(buf); /* store reference buffer size */ i = index->src_size; while (i >= 0x80) { - out[outpos++] = i | 0x80; + buf[bufpos++] = i | 0x80; i >>= 7; } - out[outpos++] = i; + buf[bufpos++] = i; /* store target buffer size */ i = trg_size; while (i >= 0x80) { - out[outpos++] = i | 0x80; + buf[bufpos++] = i | 0x80; i >>= 7; } - out[outpos++] = i; + buf[bufpos++] = i; ref_data = index->src_buf; ref_top = ref_data + index->src_size; data = trg_buf; top = (const unsigned char *) trg_buf + trg_size; - outpos++; + bufpos++; val = 0; for (i = 0; i < RABIN_WINDOW && data < top; i++, data++) { - out[outpos++] = *data; + buf[bufpos++] = *data; val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT]; } inscnt = i; @@ -350,11 +354,11 @@ git_delta_create( if (msize < 4) { if (!inscnt) - outpos++; - out[outpos++] = *data++; + bufpos++; + buf[bufpos++] = *data++; inscnt++; if (inscnt == 0x7f) { - out[outpos - inscnt - 1] = inscnt; + buf[bufpos - inscnt - 1] = inscnt; inscnt = 0; } msize = 0; @@ -368,14 +372,14 @@ git_delta_create( msize++; moff--; data--; - outpos--; + bufpos--; if (--inscnt) continue; - outpos--; /* remove count slot */ + bufpos--; /* remove count slot */ inscnt--; /* make it -1 */ break; } - out[outpos - inscnt - 1] = inscnt; + buf[bufpos - inscnt - 1] = inscnt; inscnt = 0; } @@ -383,22 +387,22 @@ git_delta_create( left = (msize < 0x10000) ? 0 : (msize - 0x10000); msize -= left; - op = out + outpos++; + op = buf + bufpos++; i = 0x80; if (moff & 0x000000ff) - out[outpos++] = moff >> 0, i |= 0x01; + buf[bufpos++] = moff >> 0, i |= 0x01; if (moff & 0x0000ff00) - out[outpos++] = moff >> 8, i |= 0x02; + buf[bufpos++] = moff >> 8, i |= 0x02; if (moff & 0x00ff0000) - out[outpos++] = moff >> 16, i |= 0x04; + buf[bufpos++] = moff >> 16, i |= 0x04; if (moff & 0xff000000) - out[outpos++] = moff >> 24, i |= 0x08; + buf[bufpos++] = moff >> 24, i |= 0x08; if (msize & 0x00ff) - out[outpos++] = msize >> 0, i |= 0x10; + buf[bufpos++] = msize >> 0, i |= 0x10; if (msize & 0xff00) - out[outpos++] = msize >> 8, i |= 0x20; + buf[bufpos++] = msize >> 8, i |= 0x20; *op = i; @@ -415,29 +419,201 @@ git_delta_create( } } - if (outpos >= outsize - MAX_OP_SIZE) { - void *tmp = out; - outsize = outsize * 3 / 2; - if (max_size && outsize >= max_size) - outsize = max_size + MAX_OP_SIZE + 1; - if (max_size && outpos > max_size) + if (bufpos >= bufsize - MAX_OP_SIZE) { + void *tmp = buf; + bufsize = bufsize * 3 / 2; + if (max_size && bufsize >= max_size) + bufsize = max_size + MAX_OP_SIZE + 1; + if (max_size && bufpos > max_size) break; - out = git__realloc(out, outsize); - if (!out) { + buf = git__realloc(buf, bufsize); + if (!buf) { git__free(tmp); - return NULL; + return -1; } } } if (inscnt) - out[outpos - inscnt - 1] = inscnt; + buf[bufpos - inscnt - 1] = inscnt; - if (max_size && outpos > max_size) { - git__free(out); - return NULL; + if (max_size && bufpos > max_size) { + giterr_set(GITERR_NOMEMORY, "delta would be larger than maximum size"); + git__free(buf); + return GIT_EBUFS; } - *delta_size = outpos; - return out; + *out_len = bufpos; + *out = buf; + return 0; +} + +/* +* Delta application was heavily cribbed from BinaryDelta.java in JGit, which +* itself was heavily cribbed from patch-delta.c in the +* GIT project. The original delta patching code was written by +* Nicolas Pitre . +*/ + +static int hdr_sz( + size_t *size, + const unsigned char **delta, + const unsigned char *end) +{ + const unsigned char *d = *delta; + size_t r = 0; + unsigned int c, shift = 0; + + do { + if (d == end) { + giterr_set(GITERR_INVALID, "truncated delta"); + return -1; + } + + c = *d++; + r |= (c & 0x7f) << shift; + shift += 7; + } while (c & 0x80); + *delta = d; + *size = r; + return 0; +} + +int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + if ((hdr_sz(base_out, &delta, delta_end) < 0) || + (hdr_sz(result_out, &delta, delta_end) < 0)) + return -1; + return 0; +} + +#define DELTA_HEADER_BUFFER_LEN 16 +int git_delta_read_header_fromstream( + size_t *base_sz, size_t *res_sz, git_packfile_stream *stream) +{ + static const size_t buffer_len = DELTA_HEADER_BUFFER_LEN; + unsigned char buffer[DELTA_HEADER_BUFFER_LEN]; + const unsigned char *delta, *delta_end; + size_t len; + ssize_t read; + + len = read = 0; + while (len < buffer_len) { + read = git_packfile_stream_read(stream, &buffer[len], buffer_len - len); + + if (read == 0) + break; + + if (read == GIT_EBUFS) + continue; + + len += read; + } + + delta = buffer; + delta_end = delta + len; + if ((hdr_sz(base_sz, &delta, delta_end) < 0) || + (hdr_sz(res_sz, &delta, delta_end) < 0)) + return -1; + + return 0; +} + +int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len) +{ + const unsigned char *delta_end = delta + delta_len; + size_t base_sz, res_sz, alloc_sz; + unsigned char *res_dp; + + *out = NULL; + *out_len = 0; + + /* Check that the base size matches the data we were given; + * if not we would underflow while accessing data from the + * base object, resulting in data corruption or segfault. + */ + if ((hdr_sz(&base_sz, &delta, delta_end) < 0) || (base_sz != base_len)) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } + + if (hdr_sz(&res_sz, &delta, delta_end) < 0) { + giterr_set(GITERR_INVALID, "Failed to apply delta. Base size does not match given data"); + return -1; + } + + GITERR_CHECK_ALLOC_ADD(&alloc_sz, res_sz, 1); + res_dp = git__malloc(alloc_sz); + GITERR_CHECK_ALLOC(res_dp); + + res_dp[res_sz] = '\0'; + *out = res_dp; + *out_len = res_sz; + + while (delta < delta_end) { + unsigned char cmd = *delta++; + if (cmd & 0x80) { + /* cmd is a copy instruction; copy from the base. + */ + size_t off = 0, len = 0; + + if (cmd & 0x01) off = *delta++; + if (cmd & 0x02) off |= *delta++ << 8UL; + if (cmd & 0x04) off |= *delta++ << 16UL; + if (cmd & 0x08) off |= *delta++ << 24UL; + + if (cmd & 0x10) len = *delta++; + if (cmd & 0x20) len |= *delta++ << 8UL; + if (cmd & 0x40) len |= *delta++ << 16UL; + if (!len) len = 0x10000; + + if (base_len < off + len || res_sz < len) + goto fail; + memcpy(res_dp, base + off, len); + res_dp += len; + res_sz -= len; + + } + else if (cmd) { + /* cmd is a literal insert instruction; copy from + * the delta stream itself. + */ + if (delta_end - delta < cmd || res_sz < cmd) + goto fail; + memcpy(res_dp, delta, cmd); + delta += cmd; + res_dp += cmd; + res_sz -= cmd; + + } + else { + /* cmd == 0 is reserved for future encodings. + */ + goto fail; + } + } + + if (delta != delta_end || res_sz) + goto fail; + return 0; + +fail: + git__free(*out); + + *out = NULL; + *out_len = 0; + + giterr_set(GITERR_INVALID, "Failed to apply delta"); + return -1; } diff --git a/src/delta.h b/src/delta.h index 4ca327992..cc9372922 100644 --- a/src/delta.h +++ b/src/delta.h @@ -6,12 +6,12 @@ #define INCLUDE_git_delta_h__ #include "common.h" +#include "pack.h" -/* opaque object for delta index */ -struct git_delta_index; +typedef struct git_delta_index git_delta_index; /* - * create_delta_index: compute index data from given buffer + * git_delta_index_init: compute index data from given buffer * * This returns a pointer to a struct delta_index that should be passed to * subsequent create_delta() calls, or to free_delta_index(). A NULL pointer @@ -19,22 +19,18 @@ struct git_delta_index; * before free_delta_index() is called. The returned pointer must be freed * using free_delta_index(). */ -extern struct git_delta_index * -git_delta_create_index(const void *buf, unsigned long bufsize); +extern int git_delta_index_init( + git_delta_index **out, const void *buf, size_t bufsize); /* - * free_delta_index: free the index created by create_delta_index() - * - * Given pointer must be what create_delta_index() returned, or NULL. + * Free the index created by git_delta_index_init() */ -extern void git_delta_free_index(struct git_delta_index *index); +extern void git_delta_index_free(git_delta_index *index); /* - * sizeof_delta_index: returns memory usage of delta index - * - * Given pointer must be what create_delta_index() returned, or NULL. + * Returns memory usage of delta index. */ -extern unsigned long git_delta_sizeof_index(struct git_delta_index *index); +extern size_t git_delta_index_size(git_delta_index *index); /* * create_delta: create a delta from given index for the given buffer @@ -46,69 +42,94 @@ extern unsigned long git_delta_sizeof_index(struct git_delta_index *index); * returned and *delta_size is updated with its size. The returned buffer * must be freed by the caller. */ -extern void *git_delta_create( +extern int git_delta_create_from_index( + void **out, + size_t *out_size, const struct git_delta_index *index, const void *buf, - unsigned long bufsize, - unsigned long *delta_size, - unsigned long max_delta_size); + size_t bufsize, + size_t max_delta_size); /* * diff_delta: create a delta from source buffer to target buffer * * If max_delta_size is non-zero and the resulting delta is to be larger - * than max_delta_size then NULL is returned. On success, a non-NULL + * than max_delta_size then GIT_EBUFS is returned. On success, a non-NULL * pointer to the buffer with the delta data is returned and *delta_size is * updated with its size. The returned buffer must be freed by the caller. */ -GIT_INLINE(void *) git_delta( - const void *src_buf, unsigned long src_bufsize, - const void *trg_buf, unsigned long trg_bufsize, - unsigned long *delta_size, - unsigned long max_delta_size) +GIT_INLINE(int) git_delta( + void **out, size_t *out_len, + const void *src_buf, size_t src_bufsize, + const void *trg_buf, size_t trg_bufsize, + size_t max_delta_size) { - struct git_delta_index *index = git_delta_create_index(src_buf, src_bufsize); - if (index) { - void *delta = git_delta_create( - index, trg_buf, trg_bufsize, delta_size, max_delta_size); - git_delta_free_index(index); - return delta; - } - return NULL; -} + git_delta_index *index; + int error = 0; -/* - * patch_delta: recreate target buffer given source buffer and delta data - * - * On success, a non-NULL pointer to the target buffer is returned and - * *trg_bufsize is updated with its size. On failure a NULL pointer is - * returned. The returned buffer must be freed by the caller. - */ -extern void *git_delta_patch( - const void *src_buf, unsigned long src_size, - const void *delta_buf, unsigned long delta_size, - unsigned long *dst_size); + *out = NULL; + *out_len = 0; + + if ((error = git_delta_index_init(&index, src_buf, src_bufsize)) < 0) + return error; + + if (index) { + error = git_delta_create_from_index(out, out_len, + index, trg_buf, trg_bufsize, max_delta_size); + + git_delta_index_free(index); + } + + return error; +} /* the smallest possible delta size is 4 bytes */ #define GIT_DELTA_SIZE_MIN 4 -/* - * This must be called twice on the delta data buffer, first to get the - * expected source buffer size, and again to get the target buffer size. +/** +* Apply a git binary delta to recover the original content. +* The caller is responsible for freeing the returned buffer. +* +* @param out the output buffer +* @param out_len the length of the output buffer +* @param base the base to copy from during copy instructions. +* @param base_len number of bytes available at base. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_apply( + void **out, + size_t *out_len, + const unsigned char *base, + size_t base_len, + const unsigned char *delta, + size_t delta_len); + +/** +* Read the header of a git binary delta. +* +* @param base_out pointer to store the base size field. +* @param result_out pointer to store the result size field. +* @param delta the delta to execute copy/insert instructions from. +* @param delta_len total number of bytes in the delta. +* @return 0 on success or an error code +*/ +extern int git_delta_read_header( + size_t *base_out, + size_t *result_out, + const unsigned char *delta, + size_t delta_len); + +/** + * Read the header of a git binary delta + * + * This variant reads just enough from the packfile stream to read the + * delta header. */ -GIT_INLINE(unsigned long) git_delta_get_hdr_size( - const unsigned char **datap, const unsigned char *top) -{ - const unsigned char *data = *datap; - unsigned long cmd, size = 0; - int i = 0; - do { - cmd = *data++; - size |= (cmd & 0x7f) << i; - i += 7; - } while (cmd & 0x80 && data < top); - *datap = data; - return size; -} +extern int git_delta_read_header_fromstream( + size_t *base_out, + size_t *result_out, + git_packfile_stream *stream); #endif diff --git a/src/describe.c b/src/describe.c index 13ddad5be..fc48fbde4 100644 --- a/src/describe.c +++ b/src/describe.c @@ -197,7 +197,7 @@ static int commit_name_dup(struct commit_name **out, struct commit_name *in) name->tag = NULL; name->path = NULL; - if (in->tag && git_object_dup((git_object **) &name->tag, (git_object *) in->tag) < 0) + if (in->tag && git_tag_dup(&name->tag, in->tag) < 0) return -1; name->path = git__strdup(in->path); diff --git a/src/diff.c b/src/diff.c index 9ac5b9250..317d49597 100644 --- a/src/diff.c +++ b/src/diff.c @@ -4,275 +4,21 @@ * This file is part of libgit2, distributed under the GNU GPL v2 with * a Linking Exception. For full terms see the included COPYING file. */ +#include "git2/version.h" #include "common.h" #include "diff.h" -#include "fileops.h" -#include "config.h" -#include "attr_file.h" -#include "filter.h" -#include "pathspec.h" +#include "diff_generate.h" +#include "patch.h" +#include "commit.h" #include "index.h" -#include "odb.h" -#include "submodule.h" -#define DIFF_FLAG_IS_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) != 0) -#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) (((DIFF)->opts.flags & (FLAG)) == 0) +#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ + (((DIFF)->opts.flags & (FLAG)) != 0) +#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ + (((DIFF)->opts.flags & (FLAG)) == 0) #define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->opts.flags = \ (VAL) ? ((DIFF)->opts.flags | (FLAG)) : ((DIFF)->opts.flags & ~(VAL)) -static git_diff_delta *diff_delta__alloc( - git_diff *diff, - git_delta_t status, - const char *path) -{ - git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); - if (!delta) - return NULL; - - delta->old_file.path = git_pool_strdup(&diff->pool, path); - if (delta->old_file.path == NULL) { - git__free(delta); - return NULL; - } - - delta->new_file.path = delta->old_file.path; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - switch (status) { - case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; - case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; - default: break; /* leave other status values alone */ - } - } - delta->status = status; - - return delta; -} - -static int diff_insert_delta( - git_diff *diff, git_diff_delta *delta, const char *matched_pathspec) -{ - int error = 0; - - if (diff->opts.notify_cb) { - error = diff->opts.notify_cb( - diff, delta, matched_pathspec, diff->opts.payload); - - if (error) { - git__free(delta); - - if (error > 0) /* positive value means to skip this delta */ - return 0; - else /* negative value means to cancel diff */ - return giterr_set_after_callback_function(error, "git_diff"); - } - } - - if ((error = git_vector_insert(&diff->deltas, delta)) < 0) - git__free(delta); - - return error; -} - -static bool diff_pathspec_match( - const char **matched_pathspec, - git_diff *diff, - const git_index_entry *entry) -{ - bool disable_pathspec_match = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); - - /* If we're disabling fnmatch, then the iterator has already applied - * the filters to the files for us and we don't have to do anything. - * However, this only applies to *files* - the iterator will include - * directories that we need to recurse into when not autoexpanding, - * so we still need to apply the pathspec match to directories. - */ - if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && - disable_pathspec_match) { - *matched_pathspec = entry->path; - return true; - } - - return git_pathspec__match( - &diff->pathspec, entry->path, disable_pathspec_match, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - matched_pathspec, NULL); -} - -static int diff_delta__from_one( - git_diff *diff, - git_delta_t status, - const git_index_entry *oitem, - const git_index_entry *nitem) -{ - const git_index_entry *entry = nitem; - bool has_old = false; - git_diff_delta *delta; - const char *matched_pathspec; - - assert((oitem != NULL) ^ (nitem != NULL)); - - if (oitem) { - entry = oitem; - has_old = true; - } - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) - has_old = !has_old; - - if ((entry->flags & GIT_IDXENTRY_VALID) != 0) - return 0; - - if (status == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) - return 0; - - if (status == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) - return 0; - - if (status == GIT_DELTA_UNREADABLE && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) - return 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, entry)) - return 0; - - delta = diff_delta__alloc(diff, status, entry->path); - GITERR_CHECK_ALLOC(delta); - - /* This fn is just for single-sided diffs */ - assert(status != GIT_DELTA_MODIFIED); - delta->nfiles = 1; - - if (has_old) { - delta->old_file.mode = entry->mode; - delta->old_file.size = entry->file_size; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->old_file.id, &entry->id); - } else /* ADDED, IGNORED, UNTRACKED */ { - delta->new_file.mode = entry->mode; - delta->new_file.size = entry->file_size; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - git_oid_cpy(&delta->new_file.id, &entry->id); - } - - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - if (has_old || !git_oid_iszero(&delta->new_file.id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static int diff_delta__from_two( - git_diff *diff, - git_delta_t status, - const git_index_entry *old_entry, - uint32_t old_mode, - const git_index_entry *new_entry, - uint32_t new_mode, - const git_oid *new_id, - const char *matched_pathspec) -{ - const git_oid *old_id = &old_entry->id; - git_diff_delta *delta; - const char *canonical_path = old_entry->path; - - if (status == GIT_DELTA_UNMODIFIED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) - return 0; - - if (!new_id) - new_id = &new_entry->id; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - uint32_t temp_mode = old_mode; - const git_index_entry *temp_entry = old_entry; - const git_oid *temp_id = old_id; - - old_entry = new_entry; - new_entry = temp_entry; - old_mode = new_mode; - new_mode = temp_mode; - old_id = new_id; - new_id = temp_id; - } - - delta = diff_delta__alloc(diff, status, canonical_path); - GITERR_CHECK_ALLOC(delta); - delta->nfiles = 2; - - if (!git_index_entry_is_conflict(old_entry)) { - delta->old_file.size = old_entry->file_size; - delta->old_file.mode = old_mode; - git_oid_cpy(&delta->old_file.id, old_id); - delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | - GIT_DIFF_FLAG_EXISTS; - } - - if (!git_index_entry_is_conflict(new_entry)) { - git_oid_cpy(&delta->new_file.id, new_id); - delta->new_file.size = new_entry->file_size; - delta->new_file.mode = new_mode; - delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; - delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; - - if (!git_oid_iszero(&new_entry->id)) - delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; - } - - return diff_insert_delta(diff, delta, matched_pathspec); -} - -static git_diff_delta *diff_delta__last_for_item( - git_diff *diff, - const git_index_entry *item) -{ - git_diff_delta *delta = git_vector_last(&diff->deltas); - if (!delta) - return NULL; - - switch (delta->status) { - case GIT_DELTA_UNMODIFIED: - case GIT_DELTA_DELETED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_ADDED: - if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_UNREADABLE: - case GIT_DELTA_UNTRACKED: - if (diff->strcomp(delta->new_file.path, item->path) == 0 && - git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - case GIT_DELTA_MODIFIED: - if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || - git_oid__cmp(&delta->new_file.id, &item->id) == 0) - return delta; - break; - default: - break; - } - - return NULL; -} - -static char *diff_strdup_prefix(git_pool *pool, const char *prefix) -{ - size_t len = strlen(prefix); - - /* append '/' at end if needed */ - if (len > 0 && prefix[len - 1] != '/') - return git_pool_strcat(pool, prefix, "/"); - else - return git_pool_strndup(pool, prefix, len + 1); -} - GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) { const char *str = delta->old_file.path; @@ -305,73 +51,7 @@ int git_diff_delta__casecmp(const void *a, const void *b) return val ? val : ((int)da->status - (int)db->status); } -GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) -{ - return delta->old_file.path ? - delta->old_file.path : delta->new_file.path; -} - -int git_diff_delta__i2w_cmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -int git_diff_delta__i2w_casecmp(const void *a, const void *b) -{ - const git_diff_delta *da = a, *db = b; - int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); - return val ? val : ((int)da->status - (int)db->status); -} - -bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta) -{ - uint32_t flags = opts ? opts->flags : 0; - - if (delta->status == GIT_DELTA_UNMODIFIED && - (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) - return true; - - if (delta->status == GIT_DELTA_IGNORED && - (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNTRACKED && - (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) - return true; - - if (delta->status == GIT_DELTA_UNREADABLE && - (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) - return true; - - return false; -} - - -static const char *diff_mnemonic_prefix( - git_iterator_type_t type, bool left_side) -{ - const char *pfx = ""; - - switch (type) { - case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break; - case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break; - case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break; - case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break; - case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break; - default: break; - } - - /* note: without a deeper look at pathspecs, there is no easy way - * to get the (o)bject / (w)ork tree mnemonics working... - */ - - return pfx; -} - -static int diff_entry_cmp(const void *a, const void *b) +int git_diff__entry_cmp(const void *a, const void *b) { const git_index_entry *entry_a = a; const git_index_entry *entry_b = b; @@ -379,7 +59,7 @@ static int diff_entry_cmp(const void *a, const void *b) return strcmp(entry_a->path, entry_b->path); } -static int diff_entry_icmp(const void *a, const void *b) +int git_diff__entry_icmp(const void *a, const void *b) { const git_index_entry *entry_a = a; const git_index_entry *entry_b = b; @@ -387,198 +67,12 @@ static int diff_entry_icmp(const void *a, const void *b) return strcasecmp(entry_a->path, entry_b->path); } -static void diff_set_ignore_case(git_diff *diff, bool ignore_case) -{ - if (!ignore_case) { - diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcmp; - diff->strncomp = git__strncmp; - diff->pfxcomp = git__prefixcmp; - diff->entrycomp = diff_entry_cmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); - } else { - diff->opts.flags |= GIT_DIFF_IGNORE_CASE; - - diff->strcomp = git__strcasecmp; - diff->strncomp = git__strncasecmp; - diff->pfxcomp = git__prefixcmp_icase; - diff->entrycomp = diff_entry_icmp; - - git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); - } - - git_vector_sort(&diff->deltas); -} - -static git_diff *diff_list_alloc( - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter) -{ - git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; - git_diff *diff = git__calloc(1, sizeof(git_diff)); - if (!diff) - return NULL; - - assert(repo && old_iter && new_iter); - - GIT_REFCOUNT_INC(diff); - diff->repo = repo; - diff->old_src = old_iter->type; - diff->new_src = new_iter->type; - memcpy(&diff->opts, &dflt, sizeof(diff->opts)); - - git_pool_init(&diff->pool, 1); - - if (git_vector_init(&diff->deltas, 0, git_diff_delta__cmp) < 0) { - git_diff_free(diff); - return NULL; - } - - /* Use case-insensitive compare if either iterator has - * the ignore_case bit set */ - diff_set_ignore_case( - diff, - git_iterator_ignore_case(old_iter) || - git_iterator_ignore_case(new_iter)); - - return diff; -} - -static int diff_list_apply_options( - git_diff *diff, - const git_diff_options *opts) -{ - git_config *cfg = NULL; - git_repository *repo = diff->repo; - git_pool *pool = &diff->pool; - int val; - - if (opts) { - /* copy user options (except case sensitivity info from iterators) */ - bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); - memcpy(&diff->opts, opts, sizeof(diff->opts)); - DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); - - /* initialize pathspec from options */ - if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) - return -1; - } - - /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) - diff->opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; - - /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) - diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; - - /* load config values that affect diff behavior */ - if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) - return val; - - if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; - - if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; - - if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; - - if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) - diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; - - /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ - - /* If not given explicit `opts`, check `diff.xyz` configs */ - if (!opts) { - int context = git_config__get_int_force(cfg, "diff.context", 3); - diff->opts.context_lines = context >= 0 ? (uint32_t)context : 3; - - /* add other defaults here */ - } - - /* Reverse src info if diff is reversed */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - git_iterator_type_t tmp_src = diff->old_src; - diff->old_src = diff->new_src; - diff->new_src = tmp_src; - } - - /* Unset UPDATE_INDEX unless diffing workdir and index */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && - (!(diff->old_src == GIT_ITERATOR_TYPE_WORKDIR || - diff->new_src == GIT_ITERATOR_TYPE_WORKDIR) || - !(diff->old_src == GIT_ITERATOR_TYPE_INDEX || - diff->new_src == GIT_ITERATOR_TYPE_INDEX))) - diff->opts.flags &= ~GIT_DIFF_UPDATE_INDEX; - - /* if ignore_submodules not explicitly set, check diff config */ - if (diff->opts.ignore_submodules <= 0) { - git_config_entry *entry; - git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); - - if (entry && git_submodule_parse_ignore( - &diff->opts.ignore_submodules, entry->value) < 0) - giterr_clear(); - git_config_entry_free(entry); - } - - /* if either prefix is not set, figure out appropriate value */ - if (!diff->opts.old_prefix || !diff->opts.new_prefix) { - const char *use_old = DIFF_OLD_PREFIX_DEFAULT; - const char *use_new = DIFF_NEW_PREFIX_DEFAULT; - - if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) - use_old = use_new = ""; - else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { - use_old = diff_mnemonic_prefix(diff->old_src, true); - use_new = diff_mnemonic_prefix(diff->new_src, false); - } - - if (!diff->opts.old_prefix) - diff->opts.old_prefix = use_old; - if (!diff->opts.new_prefix) - diff->opts.new_prefix = use_new; - } - - /* strdup prefix from pool so we're not dependent on external data */ - diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix); - diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix); - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { - const char *tmp_prefix = diff->opts.old_prefix; - diff->opts.old_prefix = diff->opts.new_prefix; - diff->opts.new_prefix = tmp_prefix; - } - - git_config_free(cfg); - - /* check strdup results for error */ - return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0; -} - -static void diff_list_free(git_diff *diff) -{ - git_vector_free_deep(&diff->deltas); - - git_pathspec__vfree(&diff->pathspec); - git_pool_clear(&diff->pool); - - git__memzero(diff, sizeof(*diff)); - git__free(diff); -} - void git_diff_free(git_diff *diff) { if (!diff) return; - GIT_REFCOUNT_DEC(diff, diff_list_free); + GIT_REFCOUNT_DEC(diff, diff->free_fn); } void git_diff_addref(git_diff *diff) @@ -586,903 +80,6 @@ void git_diff_addref(git_diff *diff) GIT_REFCOUNT_INC(diff); } -int git_diff__oid_for_file( - git_oid *out, - git_diff *diff, - const char *path, - uint16_t mode, - git_off_t size) -{ - git_index_entry entry; - - memset(&entry, 0, sizeof(entry)); - entry.mode = mode; - entry.file_size = size; - entry.path = (char *)path; - - return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); -} - -int git_diff__oid_for_entry( - git_oid *out, - git_diff *diff, - const git_index_entry *src, - uint16_t mode, - const git_oid *update_match) -{ - int error = 0; - git_buf full_path = GIT_BUF_INIT; - git_index_entry entry = *src; - git_filter_list *fl = NULL; - - memset(out, 0, sizeof(*out)); - - if (git_buf_joinpath( - &full_path, git_repository_workdir(diff->repo), entry.path) < 0) - return -1; - - if (!mode) { - struct stat st; - - diff->perf.stat_calls++; - - if (p_stat(full_path.ptr, &st) < 0) { - error = git_path_set_error(errno, entry.path, "stat"); - git_buf_free(&full_path); - return error; - } - - git_index_entry__init_from_stat( - &entry, &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); - } - - /* calculate OID for file if possible */ - if (S_ISGITLINK(mode)) { - git_submodule *sm; - - if (!git_submodule_lookup(&sm, diff->repo, entry.path)) { - const git_oid *sm_oid = git_submodule_wd_id(sm); - if (sm_oid) - git_oid_cpy(out, sm_oid); - git_submodule_free(sm); - } else { - /* if submodule lookup failed probably just in an intermediate - * state where some init hasn't happened, so ignore the error - */ - giterr_clear(); - } - } else if (S_ISLNK(mode)) { - error = git_odb__hashlink(out, full_path.ptr); - diff->perf.oid_calculations++; - } else if (!git__is_sizet(entry.file_size)) { - giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", - entry.path); - error = -1; - } else if (!(error = git_filter_list_load( - &fl, diff->repo, NULL, entry.path, - GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) - { - int fd = git_futils_open_ro(full_path.ptr); - if (fd < 0) - error = fd; - else { - error = git_odb__hashfd_filtered( - out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl); - p_close(fd); - diff->perf.oid_calculations++; - } - - git_filter_list_free(fl); - } - - /* update index for entry if requested */ - if (!error && update_match && git_oid_equal(out, update_match)) { - git_index *idx; - git_index_entry updated_entry; - - memcpy(&updated_entry, &entry, sizeof(git_index_entry)); - updated_entry.mode = mode; - git_oid_cpy(&updated_entry.id, out); - - if (!(error = git_repository_index__weakptr(&idx, diff->repo))) { - error = git_index_add(idx, &updated_entry); - diff->index_updated = true; - } - } - - git_buf_free(&full_path); - return error; -} - -typedef struct { - git_repository *repo; - git_iterator *old_iter; - git_iterator *new_iter; - const git_index_entry *oitem; - const git_index_entry *nitem; -} diff_in_progress; - -#define MODE_BITS_MASK 0000777 - -static int maybe_modified_submodule( - git_delta_t *status, - git_oid *found_oid, - git_diff *diff, - diff_in_progress *info) -{ - int error = 0; - git_submodule *sub; - unsigned int sm_status = 0; - git_submodule_ignore_t ign = diff->opts.ignore_submodules; - - *status = GIT_DELTA_UNMODIFIED; - - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || - ign == GIT_SUBMODULE_IGNORE_ALL) - return 0; - - if ((error = git_submodule_lookup( - &sub, diff->repo, info->nitem->path)) < 0) { - - /* GIT_EEXISTS means dir with .git in it was found - ignore it */ - if (error == GIT_EEXISTS) { - giterr_clear(); - error = 0; - } - return error; - } - - if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) - /* ignore it */; - else if ((error = git_submodule__status( - &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) - /* return error below */; - - /* check IS_WD_UNMODIFIED because this case is only used - * when the new side of the diff is the working directory - */ - else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) - *status = GIT_DELTA_MODIFIED; - - /* now that we have a HEAD OID, check if HEAD moved */ - else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && - !git_oid_equal(&info->oitem->id, found_oid)) - *status = GIT_DELTA_MODIFIED; - - git_submodule_free(sub); - return error; -} - -static int maybe_modified( - git_diff *diff, - diff_in_progress *info) -{ - git_oid noid; - git_delta_t status = GIT_DELTA_MODIFIED; - const git_index_entry *oitem = info->oitem; - const git_index_entry *nitem = info->nitem; - unsigned int omode = oitem->mode; - unsigned int nmode = nitem->mode; - bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); - bool modified_uncertain = false; - const char *matched_pathspec; - int error = 0; - - if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) - return 0; - - memset(&noid, 0, sizeof(noid)); - - /* on platforms with no symlinks, preserve mode of existing symlinks */ - if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && - !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) - nmode = omode; - - /* on platforms with no execmode, just preserve old mode */ - if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && - (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && - new_is_workdir) - nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); - - /* if one side is a conflict, mark the whole delta as conflicted */ - if (git_index_entry_is_conflict(oitem) || - git_index_entry_is_conflict(nitem)) { - status = GIT_DELTA_CONFLICTED; - - /* support "assume unchanged" (poorly, b/c we still stat everything) */ - } else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* support "skip worktree" index bit */ - } else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) { - status = GIT_DELTA_UNMODIFIED; - - /* if basic type of file changed, then split into delete and add */ - } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { - status = GIT_DELTA_TYPECHANGE; - } - - else if (nmode == GIT_FILEMODE_UNREADABLE) { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); - return error; - } - - else { - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - return error; - } - - /* if oids and modes match (and are valid), then file is unmodified */ - } else if (git_oid_equal(&oitem->id, &nitem->id) && - omode == nmode && - !git_oid_iszero(&oitem->id)) { - status = GIT_DELTA_UNMODIFIED; - - /* if we have an unknown OID and a workdir iterator, then check some - * circumstances that can accelerate things or need special handling - */ - } else if (git_oid_iszero(&nitem->id) && new_is_workdir) { - bool use_ctime = ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); - git_index *index; - git_iterator_index(&index, info->new_iter); - - status = GIT_DELTA_UNMODIFIED; - - if (S_ISGITLINK(nmode)) { - if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) - return error; - } - - /* if the stat data looks different, then mark modified - this just - * means that the OID will be recalculated below to confirm change - */ - else if (omode != nmode || oitem->file_size != nitem->file_size) { - status = GIT_DELTA_MODIFIED; - modified_uncertain = - (oitem->file_size <= 0 && nitem->file_size > 0); - } - else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || - (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || - oitem->ino != nitem->ino || - oitem->uid != nitem->uid || - oitem->gid != nitem->gid || - git_index_entry_newer_than_index(nitem, index)) - { - status = GIT_DELTA_MODIFIED; - modified_uncertain = true; - } - - /* if mode is GITLINK and submodules are ignored, then skip */ - } else if (S_ISGITLINK(nmode) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { - status = GIT_DELTA_UNMODIFIED; - } - - /* if we got here and decided that the files are modified, but we - * haven't calculated the OID of the new item, then calculate it now - */ - if (modified_uncertain && git_oid_iszero(&nitem->id)) { - const git_oid *update_check = - DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? - &oitem->id : NULL; - - if ((error = git_diff__oid_for_entry( - &noid, diff, nitem, nmode, update_check)) < 0) - return error; - - /* if oid matches, then mark unmodified (except submodules, where - * the filesystem content may be modified even if the oid still - * matches between the index and the workdir HEAD) - */ - if (omode == nmode && !S_ISGITLINK(omode) && - git_oid_equal(&oitem->id, &noid)) - status = GIT_DELTA_UNMODIFIED; - } - - /* If we want case changes, then break this into a delete of the old - * and an add of the new so that consumers can act accordingly (eg, - * checkout will update the case on disk.) - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && - strcmp(oitem->path, nitem->path) != 0) { - - if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) - error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); - - return error; - } - - return diff_delta__from_two( - diff, status, oitem, omode, nitem, nmode, - git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec); -} - -static bool entry_is_prefixed( - git_diff *diff, - const git_index_entry *item, - const git_index_entry *prefix_item) -{ - size_t pathlen; - - if (!item || diff->pfxcomp(item->path, prefix_item->path) != 0) - return false; - - pathlen = strlen(prefix_item->path); - - return (prefix_item->path[pathlen - 1] == '/' || - item->path[pathlen] == '\0' || - item->path[pathlen] == '/'); -} - -static int iterator_current( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance( - const git_index_entry **entry, - git_iterator *iterator) -{ - const git_index_entry *prev_entry = *entry; - int cmp, error; - - /* if we're looking for conflicts, we only want to report - * one conflict for each file, instead of all three sides. - * so if this entry is a conflict for this file, and the - * previous one was a conflict for the same file, skip it. - */ - while ((error = git_iterator_advance(entry, iterator)) == 0) { - if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || - !git_index_entry_is_conflict(prev_entry) || - !git_index_entry_is_conflict(*entry)) - break; - - cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? - strcasecmp(prev_entry->path, (*entry)->path) : - strcmp(prev_entry->path, (*entry)->path); - - if (cmp) - break; - } - - if (error == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_into( - const git_index_entry **entry, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int iterator_advance_over_with_status( - const git_index_entry **entry, - git_iterator_status_t *status, - git_iterator *iterator) -{ - int error; - - if ((error = git_iterator_advance_over_with_status( - entry, status, iterator)) == GIT_ITEROVER) { - *entry = NULL; - error = 0; - } - - return error; -} - -static int handle_unmatched_new_item( - git_diff *diff, diff_in_progress *info) -{ - int error = 0; - const git_index_entry *nitem = info->nitem; - git_delta_t delta_type = GIT_DELTA_UNTRACKED; - bool contains_oitem; - - /* check if this is a prefix of the other side */ - contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(nitem)) - delta_type = GIT_DELTA_CONFLICTED; - - /* update delta_type if this item is ignored */ - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - - if (nitem->mode == GIT_FILEMODE_TREE) { - bool recurse_into_dir = contains_oitem; - - /* check if user requests recursion into this type of dir */ - recurse_into_dir = contains_oitem || - (delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || - (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); - - /* do not advance into directories that contain a .git file */ - if (recurse_into_dir && !contains_oitem) { - git_buf *full = NULL; - if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) - return -1; - if (full && git_path_contains(full, DOT_GIT)) { - /* TODO: warning if not a valid git repository */ - recurse_into_dir = false; - } - } - - /* still have to look into untracked directories to match core git - - * with no untracked files, directory is treated as ignored - */ - if (!recurse_into_dir && - delta_type == GIT_DELTA_UNTRACKED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) - { - git_diff_delta *last; - git_iterator_status_t untracked_state; - - /* attempt to insert record for this directory */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* if delta wasn't created (because of rules), just skip ahead */ - last = diff_delta__last_for_item(diff, nitem); - if (!last) - return iterator_advance(&info->nitem, info->new_iter); - - /* iterate into dir looking for an actual untracked file */ - if ((error = iterator_advance_over_with_status( - &info->nitem, &untracked_state, info->new_iter)) < 0) - return error; - - /* if we found nothing that matched our pathlist filter, exclude */ - if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { - git_vector_pop(&diff->deltas); - git__free(last); - } - - /* if we found nothing or just ignored items, update the record */ - if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || - untracked_state == GIT_ITERATOR_STATUS_EMPTY) { - last->status = GIT_DELTA_IGNORED; - - /* remove the record if we don't want ignored records */ - if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { - git_vector_pop(&diff->deltas); - git__free(last); - } - } - - return 0; - } - - /* try to advance into directory if necessary */ - if (recurse_into_dir) { - error = iterator_advance_into(&info->nitem, info->new_iter); - - /* if real error or no error, proceed with iteration */ - if (error != GIT_ENOTFOUND) - return error; - giterr_clear(); - - /* if directory is empty, can't advance into it, so either skip - * it or ignore it - */ - if (contains_oitem) - return iterator_advance(&info->nitem, info->new_iter); - delta_type = GIT_DELTA_IGNORED; - } - } - - else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && - git_iterator_current_tree_is_ignored(info->new_iter)) - /* item contained in ignored directory, so skip over it */ - return iterator_advance(&info->nitem, info->new_iter); - - else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) { - if (delta_type != GIT_DELTA_CONFLICTED) - delta_type = GIT_DELTA_ADDED; - } - - else if (nitem->mode == GIT_FILEMODE_COMMIT) { - /* ignore things that are not actual submodules */ - if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { - giterr_clear(); - delta_type = GIT_DELTA_IGNORED; - - /* if this contains a tracked item, treat as normal TREE */ - if (contains_oitem) { - error = iterator_advance_into(&info->nitem, info->new_iter); - if (error != GIT_ENOTFOUND) - return error; - - giterr_clear(); - return iterator_advance(&info->nitem, info->new_iter); - } - } - } - - else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) - delta_type = GIT_DELTA_UNTRACKED; - else - delta_type = GIT_DELTA_UNREADABLE; - } - - /* Actually create the record for this item if necessary */ - if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) - return error; - - /* If user requested TYPECHANGE records, then check for that instead of - * just generating an ADDED/UNTRACKED record - */ - if (delta_type != GIT_DELTA_IGNORED && - DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - contains_oitem) - { - /* this entry was prefixed with a tree - make TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, nitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->old_file.mode = GIT_FILEMODE_TREE; - } - } - - return iterator_advance(&info->nitem, info->new_iter); -} - -static int handle_unmatched_old_item( - git_diff *diff, diff_in_progress *info) -{ - git_delta_t delta_type = GIT_DELTA_DELETED; - int error; - - /* update delta_type if this item is conflicted */ - if (git_index_entry_is_conflict(info->oitem)) - delta_type = GIT_DELTA_CONFLICTED; - - if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) - return error; - - /* if we are generating TYPECHANGE records then check for that - * instead of just generating a DELETE record - */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && - entry_is_prefixed(diff, info->nitem, info->oitem)) - { - /* this entry has become a tree! convert to TYPECHANGE */ - git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); - if (last) { - last->status = GIT_DELTA_TYPECHANGE; - last->new_file.mode = GIT_FILEMODE_TREE; - } - - /* If new_iter is a workdir iterator, then this situation - * will certainly be followed by a series of untracked items. - * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... - */ - if (S_ISDIR(info->nitem->mode) && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) - return iterator_advance(&info->nitem, info->new_iter); - } - - return iterator_advance(&info->oitem, info->old_iter); -} - -static int handle_matched_item( - git_diff *diff, diff_in_progress *info) -{ - int error = 0; - - if ((error = maybe_modified(diff, info)) < 0) - return error; - - if (!(error = iterator_advance(&info->oitem, info->old_iter))) - error = iterator_advance(&info->nitem, info->new_iter); - - return error; -} - -int git_diff__from_iterators( - git_diff **diff_ptr, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts) -{ - int error = 0; - diff_in_progress info; - git_diff *diff; - - *diff_ptr = NULL; - - diff = diff_list_alloc(repo, old_iter, new_iter); - GITERR_CHECK_ALLOC(diff); - - info.repo = repo; - info.old_iter = old_iter; - info.new_iter = new_iter; - - /* make iterators have matching icase behavior */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { - if ((error = git_iterator_set_ignore_case(old_iter, true)) < 0 || - (error = git_iterator_set_ignore_case(new_iter, true)) < 0) - goto cleanup; - } - - /* finish initialization */ - if ((error = diff_list_apply_options(diff, opts)) < 0) - goto cleanup; - - if ((error = iterator_current(&info.oitem, old_iter)) < 0 || - (error = iterator_current(&info.nitem, new_iter)) < 0) - goto cleanup; - - /* run iterators building diffs */ - while (!error && (info.oitem || info.nitem)) { - int cmp; - - /* report progress */ - if (opts && opts->progress_cb) { - if ((error = opts->progress_cb(diff, - info.oitem ? info.oitem->path : NULL, - info.nitem ? info.nitem->path : NULL, - opts->payload))) - break; - } - - cmp = info.oitem ? - (info.nitem ? diff->entrycomp(info.oitem, info.nitem) : -1) : 1; - - /* create DELETED records for old items not matched in new */ - if (cmp < 0) - error = handle_unmatched_old_item(diff, &info); - - /* create ADDED, TRACKED, or IGNORED records for new items not - * matched in old (and/or descend into directories as needed) - */ - else if (cmp > 0) - error = handle_unmatched_new_item(diff, &info); - - /* otherwise item paths match, so create MODIFIED record - * (or ADDED and DELETED pair if type changed) - */ - else - error = handle_matched_item(diff, &info); - } - - diff->perf.stat_calls += old_iter->stat_calls + new_iter->stat_calls; - -cleanup: - if (!error) - *diff_ptr = diff; - else - git_diff_free(diff); - - return error; -} - -#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ - git_iterator *a = NULL, *b = NULL; \ - char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \ - git_pathspec_prefix(&opts->pathspec) : NULL; \ - git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \ - b_opts = GIT_ITERATOR_OPTIONS_INIT; \ - a_opts.flags = FLAGS_FIRST; \ - a_opts.start = pfx; \ - a_opts.end = pfx; \ - b_opts.flags = FLAGS_SECOND; \ - b_opts.start = pfx; \ - b_opts.end = pfx; \ - GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \ - a_opts.pathlist.strings = opts->pathspec.strings; \ - a_opts.pathlist.count = opts->pathspec.count; \ - b_opts.pathlist.strings = opts->pathspec.strings; \ - b_opts.pathlist.count = opts->pathspec.count; \ - } \ - if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ - error = git_diff__from_iterators(diff, repo, a, b, opts); \ - git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ -} while (0) - -int git_diff_tree_to_tree( - git_diff **diff, - git_repository *repo, - git_tree *old_tree, - git_tree *new_tree, - const git_diff_options *opts) -{ - git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; - int error = 0; - - assert(diff && repo); - - /* for tree to tree diff, be case sensitive even if the index is - * currently case insensitive, unless the user explicitly asked - * for case insensitivity - */ - if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) - iflag = GIT_ITERATOR_IGNORE_CASE; - - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), iflag, - git_iterator_for_tree(&b, new_tree, &b_opts), iflag - ); - - return error; -} - -static int diff_load_index(git_index **index, git_repository *repo) -{ - int error = git_repository_index__weakptr(index, repo); - - /* reload the repository index when user did not pass one in */ - if (!error && git_index_read(*index, false) < 0) - giterr_clear(); - - return error; -} - -int git_diff_tree_to_index( - git_diff **diff, - git_repository *repo, - git_tree *old_tree, - git_index *index, - const git_diff_options *opts) -{ - git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_CONFLICTS; - bool index_ignore_case = false; - int error = 0; - - assert(diff && repo); - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - index_ignore_case = index->ignore_case; - - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), iflag, - git_iterator_for_index(&b, repo, index, &b_opts), iflag - ); - - /* if index is in case-insensitive order, re-sort deltas to match */ - if (!error && index_ignore_case) - diff_set_ignore_case(*diff, true); - - return error; -} - -int git_diff_index_to_workdir( - git_diff **diff, - git_repository *repo, - git_index *index, - const git_diff_options *opts) -{ - int error = 0; - - assert(diff && repo); - - if (!index && (error = diff_load_index(&index, repo)) < 0) - return error; - - DIFF_FROM_ITERATORS( - git_iterator_for_index(&a, repo, index, &a_opts), - GIT_ITERATOR_INCLUDE_CONFLICTS, - - git_iterator_for_workdir(&b, repo, index, NULL, &b_opts), - GIT_ITERATOR_DONT_AUTOEXPAND - ); - - if (!error && DIFF_FLAG_IS_SET(*diff, GIT_DIFF_UPDATE_INDEX) && (*diff)->index_updated) - error = git_index_write(index); - - return error; -} - -int git_diff_tree_to_workdir( - git_diff **diff, - git_repository *repo, - git_tree *old_tree, - const git_diff_options *opts) -{ - int error = 0; - git_index *index; - - assert(diff && repo); - - if ((error = git_repository_index__weakptr(&index, repo))) - return error; - - DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, &a_opts), 0, - git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND - ); - - return error; -} - -int git_diff_tree_to_workdir_with_index( - git_diff **diff, - git_repository *repo, - git_tree *old_tree, - const git_diff_options *opts) -{ - int error = 0; - git_diff *d1 = NULL, *d2 = NULL; - git_index *index = NULL; - - assert(diff && repo); - - if ((error = diff_load_index(&index, repo)) < 0) - return error; - - if (!(error = git_diff_tree_to_index(&d1, repo, old_tree, index, opts)) && - !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) - error = git_diff_merge(d1, d2); - - git_diff_free(d2); - - if (error) { - git_diff_free(d1); - d1 = NULL; - } - - *diff = d1; - return error; -} - -int git_diff_index_to_index( - git_diff **diff, - git_repository *repo, - git_index *old_index, - git_index *new_index, - const git_diff_options *opts) -{ - int error = 0; - - assert(diff && old_index && new_index); - - DIFF_FROM_ITERATORS( - git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE, - git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE - ); - - /* if index is in case-insensitive order, re-sort deltas to match */ - if (!error && (old_index->ignore_case || new_index->ignore_case)) - diff_set_ignore_case(*diff, true); - - return error; -} - size_t git_diff_num_deltas(const git_diff *diff) { assert(diff); @@ -1523,137 +120,6 @@ int git_diff_get_perfdata(git_diff_perfdata *out, const git_diff *diff) return 0; } -int git_diff__paired_foreach( - git_diff *head2idx, - git_diff *idx2wd, - int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), - void *payload) -{ - int cmp, error = 0; - git_diff_delta *h2i, *i2w; - size_t i, j, i_max, j_max; - int (*strcomp)(const char *, const char *) = git__strcmp; - bool h2i_icase, i2w_icase, icase_mismatch; - - i_max = head2idx ? head2idx->deltas.length : 0; - j_max = idx2wd ? idx2wd->deltas.length : 0; - if (!i_max && !j_max) - return 0; - - /* At some point, tree-to-index diffs will probably never ignore case, - * even if that isn't true now. Index-to-workdir diffs may or may not - * ignore case, but the index filename for the idx2wd diff should - * still be using the canonical case-preserving name. - * - * Therefore the main thing we need to do here is make sure the diffs - * are traversed in a compatible order. To do this, we temporarily - * resort a mismatched diff to get the order correct. - * - * In order to traverse renames in the index->workdir, we need to - * ensure that we compare the index name on both sides, so we - * always sort by the old name in the i2w list. - */ - h2i_icase = head2idx != NULL && - (head2idx->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; - - i2w_icase = idx2wd != NULL && - (idx2wd->opts.flags & GIT_DIFF_IGNORE_CASE) != 0; - - icase_mismatch = - (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); - - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); - git_vector_sort(&head2idx->deltas); - } - - if (i2w_icase && !icase_mismatch) { - strcomp = git__strcasecmp; - - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp); - git_vector_sort(&idx2wd->deltas); - } else if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp); - git_vector_sort(&idx2wd->deltas); - } - - for (i = 0, j = 0; i < i_max || j < j_max; ) { - h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; - i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; - - cmp = !i2w ? -1 : !h2i ? 1 : - strcomp(h2i->new_file.path, i2w->old_file.path); - - if (cmp < 0) { - i++; i2w = NULL; - } else if (cmp > 0) { - j++; h2i = NULL; - } else { - i++; j++; - } - - if ((error = cb(h2i, i2w, payload)) != 0) { - giterr_set_after_callback(error); - break; - } - } - - /* restore case-insensitive delta sort */ - if (icase_mismatch && h2i_icase) { - git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); - git_vector_sort(&head2idx->deltas); - } - - /* restore idx2wd sort by new path */ - if (idx2wd != NULL) { - git_vector_set_cmp(&idx2wd->deltas, - i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); - git_vector_sort(&idx2wd->deltas); - } - - return error; -} - -int git_diff__commit( - git_diff **diff, - git_repository *repo, - const git_commit *commit, - const git_diff_options *opts) -{ - git_commit *parent = NULL; - git_diff *commit_diff = NULL; - git_tree *old_tree = NULL, *new_tree = NULL; - size_t parents; - int error = 0; - - if ((parents = git_commit_parentcount(commit)) > 1) { - char commit_oidstr[GIT_OID_HEXSZ + 1]; - - error = -1; - giterr_set(GITERR_INVALID, "Commit %s is a merge commit", - git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); - goto on_error; - } - - if (parents > 0) - if ((error = git_commit_parent(&parent, commit, 0)) < 0 || - (error = git_commit_tree(&old_tree, parent)) < 0) - goto on_error; - - if ((error = git_commit_tree(&new_tree, commit)) < 0 || - (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) - goto on_error; - - *diff = commit_diff; - -on_error: - git_tree_free(new_tree); - git_tree_free(old_tree); - git_commit_free(parent); - - return error; -} - int git_diff_format_email__append_header_tobuf( git_buf *out, const git_oid *id, @@ -1671,7 +137,8 @@ int git_diff_format_email__append_header_tobuf( git_oid_fmt(idstr, id); idstr[GIT_OID_HEXSZ] = '\0'; - if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), &author->when)) < 0) + if ((error = git__date_rfc2822_fmt(date_str, sizeof(date_str), + &author->when)) < 0) return error; error = git_buf_printf(out, @@ -1690,7 +157,8 @@ int git_diff_format_email__append_header_tobuf( if (total_patches == 1) { error = git_buf_puts(out, "[PATCH] "); } else { - error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", patch_no, total_patches); + error = git_buf_printf(out, "[PATCH %"PRIuZ"/%"PRIuZ"] ", + patch_no, total_patches); } if (error < 0) @@ -1748,16 +216,24 @@ int git_diff_format_email( assert(out && diff && opts); assert(opts->summary && opts->id && opts->author); - GITERR_CHECK_VERSION(opts, GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, "git_format_email_options"); + GITERR_CHECK_VERSION(opts, + GIT_DIFF_FORMAT_EMAIL_OPTIONS_VERSION, + "git_format_email_options"); - if ((ignore_marker = opts->flags & GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) == false) { + ignore_marker = (opts->flags & + GIT_DIFF_FORMAT_EMAIL_EXCLUDE_SUBJECT_PATCH_MARKER) != 0; + + if (!ignore_marker) { if (opts->patch_no > opts->total_patches) { - giterr_set(GITERR_INVALID, "patch %"PRIuZ" out of range. max %"PRIuZ, opts->patch_no, opts->total_patches); + giterr_set(GITERR_INVALID, + "patch %"PRIuZ" out of range. max %"PRIuZ, + opts->patch_no, opts->total_patches); return -1; } if (opts->patch_no == 0) { - giterr_set(GITERR_INVALID, "invalid patch no %"PRIuZ". should be >0", opts->patch_no); + giterr_set(GITERR_INVALID, + "invalid patch no %"PRIuZ". should be >0", opts->patch_no); return -1; } } @@ -1782,8 +258,8 @@ int git_diff_format_email( } error = git_diff_format_email__append_header_tobuf(out, - opts->id, opts->author, summary == NULL ? opts->summary : summary, - opts->body, opts->patch_no, opts->total_patches, ignore_marker); + opts->id, opts->author, summary == NULL ? opts->summary : summary, + opts->body, opts->patch_no, opts->total_patches, ignore_marker); if (error < 0) goto on_error; @@ -1816,7 +292,8 @@ int git_diff_commit_as_email( const git_diff_options *diff_opts) { git_diff *diff = NULL; - git_diff_format_email_options opts = GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; + git_diff_format_email_options opts = + GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT; int error; assert (out && repo && commit); @@ -1861,3 +338,4 @@ int git_diff_format_email_init_options( GIT_DIFF_FORMAT_EMAIL_OPTIONS_INIT); return 0; } + diff --git a/src/diff.h b/src/diff.h index 47743f88b..5750d2a09 100644 --- a/src/diff.h +++ b/src/diff.h @@ -8,6 +8,7 @@ #define INCLUDE_diff_h__ #include "git2/diff.h" +#include "git2/patch.h" #include "git2/sys/diff.h" #include "git2/oid.h" @@ -22,68 +23,32 @@ #define DIFF_OLD_PREFIX_DEFAULT "a/" #define DIFF_NEW_PREFIX_DEFAULT "b/" -enum { - GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ - GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ - GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ - GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ - GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ -}; - -#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) -#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) - -enum { - GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ - GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ - GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ - GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ - GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ - GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ - - GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ - GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ - GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), - GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), - GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20), -}; - -#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) - -#define GIT_DIFF__VERBOSE (1 << 30) +typedef enum { + GIT_DIFF_TYPE_UNKNOWN = 0, + GIT_DIFF_TYPE_GENERATED = 1, + GIT_DIFF_TYPE_PARSED = 2, +} git_diff_origin_t; struct git_diff { git_refcount rc; git_repository *repo; + git_diff_origin_t type; git_diff_options opts; - git_vector pathspec; git_vector deltas; /* vector of git_diff_delta */ git_pool pool; git_iterator_type_t old_src; git_iterator_type_t new_src; - uint32_t diffcaps; git_diff_perfdata perf; - bool index_updated; int (*strcomp)(const char *, const char *); int (*strncomp)(const char *, const char *, size_t); int (*pfxcomp)(const char *str, const char *pfx); int (*entrycomp)(const void *a, const void *b); + + int (*patch_fn)(git_patch **out, git_diff *diff, size_t idx); + void (*free_fn)(git_diff *diff); }; -extern void git_diff__cleanup_modes( - uint32_t diffcaps, uint32_t *omode, uint32_t *nmode); - -extern void git_diff_addref(git_diff *diff); - -extern int git_diff_delta__cmp(const void *a, const void *b); -extern int git_diff_delta__casecmp(const void *a, const void *b); - -extern const char *git_diff_delta__path(const git_diff_delta *delta); - -extern bool git_diff_delta__should_skip( - const git_diff_options *opts, const git_diff_delta *delta); - extern int git_diff_delta__format_file_header( git_buf *out, const git_diff_delta *delta, @@ -91,84 +56,11 @@ extern int git_diff_delta__format_file_header( const char *newpfx, int oid_strlen); -extern int git_diff__oid_for_file( - git_oid *out, git_diff *, const char *, uint16_t, git_off_t); -extern int git_diff__oid_for_entry( - git_oid *out, git_diff *, const git_index_entry *, uint16_t, const git_oid *update); +extern int git_diff_delta__cmp(const void *a, const void *b); +extern int git_diff_delta__casecmp(const void *a, const void *b); -extern int git_diff__from_iterators( - git_diff **diff_ptr, - git_repository *repo, - git_iterator *old_iter, - git_iterator *new_iter, - const git_diff_options *opts); - -extern int git_diff__paired_foreach( - git_diff *idx2head, - git_diff *wd2idx, - int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), - void *payload); - -extern int git_diff_find_similar__hashsig_for_file( - void **out, const git_diff_file *f, const char *path, void *p); - -extern int git_diff_find_similar__hashsig_for_buf( - void **out, const git_diff_file *f, const char *buf, size_t len, void *p); - -extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); - -extern int git_diff_find_similar__calc_similarity( - int *score, void *siga, void *sigb, void *payload); - -extern int git_diff__commit( - git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); - -/* Merge two `git_diff`s according to the callback given by `cb`. */ - -typedef git_diff_delta *(*git_diff__merge_cb)( - const git_diff_delta *left, - const git_diff_delta *right, - git_pool *pool); - -extern int git_diff__merge( - git_diff *onto, const git_diff *from, git_diff__merge_cb cb); - -extern git_diff_delta *git_diff__merge_like_cgit( - const git_diff_delta *a, - const git_diff_delta *b, - git_pool *pool); - -/* Duplicate a `git_diff_delta` out of the `git_pool` */ -extern git_diff_delta *git_diff__delta_dup( - const git_diff_delta *d, git_pool *pool); - -/* - * Sometimes a git_diff_file will have a zero size; this attempts to - * fill in the size without loading the blob if possible. If that is - * not possible, then it will return the git_odb_object that had to be - * loaded and the caller can use it or dispose of it as needed. - */ -GIT_INLINE(int) git_diff_file__resolve_zero_size( - git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) -{ - int error; - git_odb *odb; - size_t len; - git_otype type; - - if ((error = git_repository_odb(&odb, repo)) < 0) - return error; - - error = git_odb__read_header_or_object( - odb_obj, &len, &type, odb, &file->id); - - git_odb_free(odb); - - if (!error) - file->size = (git_off_t)len; - - return error; -} +extern int git_diff__entry_cmp(const void *a, const void *b); +extern int git_diff__entry_icmp(const void *a, const void *b); #endif diff --git a/src/diff_driver.c b/src/diff_driver.c index bc3518991..1a7f09a44 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -9,7 +9,6 @@ #include "git2/attr.h" #include "diff.h" -#include "diff_patch.h" #include "diff_driver.h" #include "strmap.h" #include "map.h" @@ -59,7 +58,7 @@ static git_diff_driver global_drivers[3] = { { DIFF_DRIVER_TEXT, GIT_DIFF_FORCE_TEXT, 0 }, }; -git_diff_driver_registry *git_diff_driver_registry_new() +git_diff_driver_registry *git_diff_driver_registry_new(void) { git_diff_driver_registry *reg = git__calloc(1, sizeof(git_diff_driver_registry)); @@ -115,7 +114,7 @@ static int diff_driver_add_patterns( if (error < 0) break; - if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) != 0) { + if ((error = p_regcomp(&pat->re, buf.ptr, regex_flags)) != 0) { /* * TODO: issue a warning */ @@ -211,7 +210,7 @@ static int git_diff_driver_builtin( goto done; if (ddef->words && - (error = regcomp( + (error = p_regcomp( &drv->word_pattern, ddef->words, ddef->flags | REG_EXTENDED))) { error = giterr_set_regex(&drv->word_pattern, error); @@ -315,7 +314,7 @@ static int git_diff_driver_load( goto done; if (!ce || !ce->value) /* no diff..wordregex, so just continue */; - else if (!(error = regcomp(&drv->word_pattern, ce->value, REG_EXTENDED))) + else if (!(error = p_regcomp(&drv->word_pattern, ce->value, REG_EXTENDED))) found_driver = true; else { /* TODO: warn about bad regex instead of failure */ @@ -520,4 +519,3 @@ void git_diff_find_context_clear(git_diff_find_context_payload *payload) payload->driver = NULL; } } - diff --git a/src/diff_file.c b/src/diff_file.c index ecc34cf55..cc1029038 100644 --- a/src/diff_file.c +++ b/src/diff_file.c @@ -8,6 +8,7 @@ #include "git2/blob.h" #include "git2/submodule.h" #include "diff.h" +#include "diff_generate.h" #include "diff_file.h" #include "odb.h" #include "fileops.h" @@ -149,12 +150,14 @@ int git_diff_file_content__init_from_src( if (src->blob) { fc->file->size = git_blob_rawsize(src->blob); git_oid_cpy(&fc->file->id, git_blob_id(src->blob)); + fc->file->id_abbrev = GIT_OID_HEXSZ; fc->map.len = (size_t)fc->file->size; fc->map.data = (char *)git_blob_rawcontent(src->blob); } else { fc->file->size = src->buflen; git_odb_hash(&fc->file->id, src->buf, src->buflen, GIT_OBJ_BLOB); + fc->file->id_abbrev = GIT_OID_HEXSZ; fc->map.len = src->buflen; fc->map.data = (char *)src->buf; diff --git a/src/diff_generate.c b/src/diff_generate.c new file mode 100644 index 000000000..06f9b19c7 --- /dev/null +++ b/src/diff_generate.c @@ -0,0 +1,1613 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "diff.h" +#include "diff_generate.h" +#include "patch_generate.h" +#include "fileops.h" +#include "config.h" +#include "attr_file.h" +#include "filter.h" +#include "pathspec.h" +#include "index.h" +#include "odb.h" +#include "submodule.h" + +#define DIFF_FLAG_IS_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) != 0) +#define DIFF_FLAG_ISNT_SET(DIFF,FLAG) \ + (((DIFF)->base.opts.flags & (FLAG)) == 0) +#define DIFF_FLAG_SET(DIFF,FLAG,VAL) (DIFF)->base.opts.flags = \ + (VAL) ? ((DIFF)->base.opts.flags | (FLAG)) : \ + ((DIFF)->base.opts.flags & ~(VAL)) + +typedef struct { + struct git_diff base; + + git_vector pathspec; + + uint32_t diffcaps; + bool index_updated; +} git_diff_generated; + +static git_diff_delta *diff_delta__alloc( + git_diff_generated *diff, + git_delta_t status, + const char *path) +{ + git_diff_delta *delta = git__calloc(1, sizeof(git_diff_delta)); + if (!delta) + return NULL; + + delta->old_file.path = git_pool_strdup(&diff->base.pool, path); + if (delta->old_file.path == NULL) { + git__free(delta); + return NULL; + } + + delta->new_file.path = delta->old_file.path; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + switch (status) { + case GIT_DELTA_ADDED: status = GIT_DELTA_DELETED; break; + case GIT_DELTA_DELETED: status = GIT_DELTA_ADDED; break; + default: break; /* leave other status values alone */ + } + } + delta->status = status; + + return delta; +} + +static int diff_insert_delta( + git_diff_generated *diff, + git_diff_delta *delta, + const char *matched_pathspec) +{ + int error = 0; + + if (diff->base.opts.notify_cb) { + error = diff->base.opts.notify_cb( + &diff->base, delta, matched_pathspec, diff->base.opts.payload); + + if (error) { + git__free(delta); + + if (error > 0) /* positive value means to skip this delta */ + return 0; + else /* negative value means to cancel diff */ + return giterr_set_after_callback_function(error, "git_diff"); + } + } + + if ((error = git_vector_insert(&diff->base.deltas, delta)) < 0) + git__free(delta); + + return error; +} + +static bool diff_pathspec_match( + const char **matched_pathspec, + git_diff_generated *diff, + const git_index_entry *entry) +{ + bool disable_pathspec_match = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH); + + /* If we're disabling fnmatch, then the iterator has already applied + * the filters to the files for us and we don't have to do anything. + * However, this only applies to *files* - the iterator will include + * directories that we need to recurse into when not autoexpanding, + * so we still need to apply the pathspec match to directories. + */ + if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && + disable_pathspec_match) { + *matched_pathspec = entry->path; + return true; + } + + return git_pathspec__match( + &diff->pathspec, entry->path, disable_pathspec_match, + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), + matched_pathspec, NULL); +} + +static int diff_delta__from_one( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *oitem, + const git_index_entry *nitem) +{ + const git_index_entry *entry = nitem; + bool has_old = false; + git_diff_delta *delta; + const char *matched_pathspec; + + assert((oitem != NULL) ^ (nitem != NULL)); + + if (oitem) { + entry = oitem; + has_old = true; + } + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) + has_old = !has_old; + + if ((entry->flags & GIT_IDXENTRY_VALID) != 0) + return 0; + + if (status == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) + return 0; + + if (status == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNTRACKED)) + return 0; + + if (status == GIT_DELTA_UNREADABLE && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) + return 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, entry)) + return 0; + + delta = diff_delta__alloc(diff, status, entry->path); + GITERR_CHECK_ALLOC(delta); + + /* This fn is just for single-sided diffs */ + assert(status != GIT_DELTA_MODIFIED); + delta->nfiles = 1; + + if (has_old) { + delta->old_file.mode = entry->mode; + delta->old_file.size = entry->file_size; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->old_file.id, &entry->id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + } else /* ADDED, IGNORED, UNTRACKED */ { + delta->new_file.mode = entry->mode; + delta->new_file.size = entry->file_size; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + git_oid_cpy(&delta->new_file.id, &entry->id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + } + + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + if (has_old || !git_oid_iszero(&delta->new_file.id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static int diff_delta__from_two( + git_diff_generated *diff, + git_delta_t status, + const git_index_entry *old_entry, + uint32_t old_mode, + const git_index_entry *new_entry, + uint32_t new_mode, + const git_oid *new_id, + const char *matched_pathspec) +{ + const git_oid *old_id = &old_entry->id; + git_diff_delta *delta; + const char *canonical_path = old_entry->path; + + if (status == GIT_DELTA_UNMODIFIED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNMODIFIED)) + return 0; + + if (!new_id) + new_id = &new_entry->id; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + uint32_t temp_mode = old_mode; + const git_index_entry *temp_entry = old_entry; + const git_oid *temp_id = old_id; + + old_entry = new_entry; + new_entry = temp_entry; + old_mode = new_mode; + new_mode = temp_mode; + old_id = new_id; + new_id = temp_id; + } + + delta = diff_delta__alloc(diff, status, canonical_path); + GITERR_CHECK_ALLOC(delta); + delta->nfiles = 2; + + if (!git_index_entry_is_conflict(old_entry)) { + delta->old_file.size = old_entry->file_size; + delta->old_file.mode = old_mode; + git_oid_cpy(&delta->old_file.id, old_id); + delta->old_file.id_abbrev = GIT_OID_HEXSZ; + delta->old_file.flags |= GIT_DIFF_FLAG_VALID_ID | + GIT_DIFF_FLAG_EXISTS; + } + + if (!git_index_entry_is_conflict(new_entry)) { + git_oid_cpy(&delta->new_file.id, new_id); + delta->new_file.id_abbrev = GIT_OID_HEXSZ; + delta->new_file.size = new_entry->file_size; + delta->new_file.mode = new_mode; + delta->old_file.flags |= GIT_DIFF_FLAG_EXISTS; + delta->new_file.flags |= GIT_DIFF_FLAG_EXISTS; + + if (!git_oid_iszero(&new_entry->id)) + delta->new_file.flags |= GIT_DIFF_FLAG_VALID_ID; + } + + return diff_insert_delta(diff, delta, matched_pathspec); +} + +static git_diff_delta *diff_delta__last_for_item( + git_diff_generated *diff, + const git_index_entry *item) +{ + git_diff_delta *delta = git_vector_last(&diff->base.deltas); + if (!delta) + return NULL; + + switch (delta->status) { + case GIT_DELTA_UNMODIFIED: + case GIT_DELTA_DELETED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_ADDED: + if (git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_UNREADABLE: + case GIT_DELTA_UNTRACKED: + if (diff->base.strcomp(delta->new_file.path, item->path) == 0 && + git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + case GIT_DELTA_MODIFIED: + if (git_oid__cmp(&delta->old_file.id, &item->id) == 0 || + git_oid__cmp(&delta->new_file.id, &item->id) == 0) + return delta; + break; + default: + break; + } + + return NULL; +} + +static char *diff_strdup_prefix(git_pool *pool, const char *prefix) +{ + size_t len = strlen(prefix); + + /* append '/' at end if needed */ + if (len > 0 && prefix[len - 1] != '/') + return git_pool_strcat(pool, prefix, "/"); + else + return git_pool_strndup(pool, prefix, len + 1); +} + +GIT_INLINE(const char *) diff_delta__i2w_path(const git_diff_delta *delta) +{ + return delta->old_file.path ? + delta->old_file.path : delta->new_file.path; +} + +int git_diff_delta__i2w_cmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +int git_diff_delta__i2w_casecmp(const void *a, const void *b) +{ + const git_diff_delta *da = a, *db = b; + int val = strcasecmp(diff_delta__i2w_path(da), diff_delta__i2w_path(db)); + return val ? val : ((int)da->status - (int)db->status); +} + +bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta) +{ + uint32_t flags = opts ? opts->flags : 0; + + if (delta->status == GIT_DELTA_UNMODIFIED && + (flags & GIT_DIFF_INCLUDE_UNMODIFIED) == 0) + return true; + + if (delta->status == GIT_DELTA_IGNORED && + (flags & GIT_DIFF_INCLUDE_IGNORED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNTRACKED && + (flags & GIT_DIFF_INCLUDE_UNTRACKED) == 0) + return true; + + if (delta->status == GIT_DELTA_UNREADABLE && + (flags & GIT_DIFF_INCLUDE_UNREADABLE) == 0) + return true; + + return false; +} + + +static const char *diff_mnemonic_prefix( + git_iterator_type_t type, bool left_side) +{ + const char *pfx = ""; + + switch (type) { + case GIT_ITERATOR_TYPE_EMPTY: pfx = "c"; break; + case GIT_ITERATOR_TYPE_TREE: pfx = "c"; break; + case GIT_ITERATOR_TYPE_INDEX: pfx = "i"; break; + case GIT_ITERATOR_TYPE_WORKDIR: pfx = "w"; break; + case GIT_ITERATOR_TYPE_FS: pfx = left_side ? "1" : "2"; break; + default: break; + } + + /* note: without a deeper look at pathspecs, there is no easy way + * to get the (o)bject / (w)ork tree mnemonics working... + */ + + return pfx; +} + +void git_diff__set_ignore_case(git_diff *diff, bool ignore_case) +{ + if (!ignore_case) { + diff->opts.flags &= ~GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcmp; + diff->strncomp = git__strncmp; + diff->pfxcomp = git__prefixcmp; + diff->entrycomp = git_diff__entry_cmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__cmp); + } else { + diff->opts.flags |= GIT_DIFF_IGNORE_CASE; + + diff->strcomp = git__strcasecmp; + diff->strncomp = git__strncasecmp; + diff->pfxcomp = git__prefixcmp_icase; + diff->entrycomp = git_diff__entry_icmp; + + git_vector_set_cmp(&diff->deltas, git_diff_delta__casecmp); + } + + git_vector_sort(&diff->deltas); +} + +static void diff_generated_free(git_diff *d) +{ + git_diff_generated *diff = (git_diff_generated *)d; + + git_vector_free_deep(&diff->base.deltas); + + git_pathspec__vfree(&diff->pathspec); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_generated *diff_generated_alloc( + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter) +{ + git_diff_generated *diff; + git_diff_options dflt = GIT_DIFF_OPTIONS_INIT; + + assert(repo && old_iter && new_iter); + + if ((diff = git__calloc(1, sizeof(git_diff_generated))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(diff); + diff->base.type = GIT_DIFF_TYPE_GENERATED; + diff->base.repo = repo; + diff->base.old_src = old_iter->type; + diff->base.new_src = new_iter->type; + diff->base.patch_fn = git_patch_generated_from_diff; + diff->base.free_fn = diff_generated_free; + memcpy(&diff->base.opts, &dflt, sizeof(git_diff_options)); + + git_pool_init(&diff->base.pool, 1); + + if (git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + /* Use case-insensitive compare if either iterator has + * the ignore_case bit set */ + git_diff__set_ignore_case( + &diff->base, + git_iterator_ignore_case(old_iter) || + git_iterator_ignore_case(new_iter)); + + return diff; +} + +static int diff_generated_apply_options( + git_diff_generated *diff, + const git_diff_options *opts) +{ + git_config *cfg = NULL; + git_repository *repo = diff->base.repo; + git_pool *pool = &diff->base.pool; + int val; + + if (opts) { + /* copy user options (except case sensitivity info from iterators) */ + bool icase = DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE); + memcpy(&diff->base.opts, opts, sizeof(diff->base.opts)); + DIFF_FLAG_SET(diff, GIT_DIFF_IGNORE_CASE, icase); + + /* initialize pathspec from options */ + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) + return -1; + } + + /* flag INCLUDE_TYPECHANGE_TREES implies INCLUDE_TYPECHANGE */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_TYPECHANGE; + + /* flag INCLUDE_UNTRACKED_CONTENT implies INCLUDE_UNTRACKED */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_SHOW_UNTRACKED_CONTENT)) + diff->base.opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; + + /* load config values that affect diff behavior */ + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) + return val; + + if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) + diff->diffcaps |= GIT_DIFFCAPS_HAS_SYMLINKS; + + if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) + diff->diffcaps |= GIT_DIFFCAPS_IGNORE_STAT; + + if ((diff->base.opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && + !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) + diff->diffcaps |= GIT_DIFFCAPS_TRUST_MODE_BITS; + + if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) + diff->diffcaps |= GIT_DIFFCAPS_TRUST_CTIME; + + /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ + + /* If not given explicit `opts`, check `diff.xyz` configs */ + if (!opts) { + int context = git_config__get_int_force(cfg, "diff.context", 3); + diff->base.opts.context_lines = context >= 0 ? (uint32_t)context : 3; + + /* add other defaults here */ + } + + /* Reverse src info if diff is reversed */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + git_iterator_type_t tmp_src = diff->base.old_src; + diff->base.old_src = diff->base.new_src; + diff->base.new_src = tmp_src; + } + + /* Unset UPDATE_INDEX unless diffing workdir and index */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && + (!(diff->base.old_src == GIT_ITERATOR_TYPE_WORKDIR || + diff->base.new_src == GIT_ITERATOR_TYPE_WORKDIR) || + !(diff->base.old_src == GIT_ITERATOR_TYPE_INDEX || + diff->base.new_src == GIT_ITERATOR_TYPE_INDEX))) + diff->base.opts.flags &= ~GIT_DIFF_UPDATE_INDEX; + + /* if ignore_submodules not explicitly set, check diff config */ + if (diff->base.opts.ignore_submodules <= 0) { + git_config_entry *entry; + git_config__lookup_entry(&entry, cfg, "diff.ignoresubmodules", true); + + if (entry && git_submodule_parse_ignore( + &diff->base.opts.ignore_submodules, entry->value) < 0) + giterr_clear(); + git_config_entry_free(entry); + } + + /* if either prefix is not set, figure out appropriate value */ + if (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) { + const char *use_old = DIFF_OLD_PREFIX_DEFAULT; + const char *use_new = DIFF_NEW_PREFIX_DEFAULT; + + if (git_config__get_bool_force(cfg, "diff.noprefix", 0)) + use_old = use_new = ""; + else if (git_config__get_bool_force(cfg, "diff.mnemonicprefix", 0)) { + use_old = diff_mnemonic_prefix(diff->base.old_src, true); + use_new = diff_mnemonic_prefix(diff->base.new_src, false); + } + + if (!diff->base.opts.old_prefix) + diff->base.opts.old_prefix = use_old; + if (!diff->base.opts.new_prefix) + diff->base.opts.new_prefix = use_new; + } + + /* strdup prefix from pool so we're not dependent on external data */ + diff->base.opts.old_prefix = diff_strdup_prefix(pool, diff->base.opts.old_prefix); + diff->base.opts.new_prefix = diff_strdup_prefix(pool, diff->base.opts.new_prefix); + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { + const char *tmp_prefix = diff->base.opts.old_prefix; + diff->base.opts.old_prefix = diff->base.opts.new_prefix; + diff->base.opts.new_prefix = tmp_prefix; + } + + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->base.opts.old_prefix || !diff->base.opts.new_prefix) ? -1 : 0; +} + +int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_off_t size) +{ + git_index_entry entry; + + memset(&entry, 0, sizeof(entry)); + entry.mode = mode; + entry.file_size = size; + entry.path = (char *)path; + + return git_diff__oid_for_entry(out, diff, &entry, mode, NULL); +} + +int git_diff__oid_for_entry( + git_oid *out, + git_diff *d, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match) +{ + git_diff_generated *diff; + git_buf full_path = GIT_BUF_INIT; + git_index_entry entry = *src; + git_filter_list *fl = NULL; + int error = 0; + + assert(d->type == GIT_DIFF_TYPE_GENERATED); + diff = (git_diff_generated *)d; + + memset(out, 0, sizeof(*out)); + + if (git_buf_joinpath(&full_path, + git_repository_workdir(diff->base.repo), entry.path) < 0) + return -1; + + if (!mode) { + struct stat st; + + diff->base.perf.stat_calls++; + + if (p_stat(full_path.ptr, &st) < 0) { + error = git_path_set_error(errno, entry.path, "stat"); + git_buf_free(&full_path); + return error; + } + + git_index_entry__init_from_stat(&entry, + &st, (diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) != 0); + } + + /* calculate OID for file if possible */ + if (S_ISGITLINK(mode)) { + git_submodule *sm; + + if (!git_submodule_lookup(&sm, diff->base.repo, entry.path)) { + const git_oid *sm_oid = git_submodule_wd_id(sm); + if (sm_oid) + git_oid_cpy(out, sm_oid); + git_submodule_free(sm); + } else { + /* if submodule lookup failed probably just in an intermediate + * state where some init hasn't happened, so ignore the error + */ + giterr_clear(); + } + } else if (S_ISLNK(mode)) { + error = git_odb__hashlink(out, full_path.ptr); + diff->base.perf.oid_calculations++; + } else if (!git__is_sizet(entry.file_size)) { + giterr_set(GITERR_OS, "File size overflow (for 32-bits) on '%s'", + entry.path); + error = -1; + } else if (!(error = git_filter_list_load(&fl, + diff->base.repo, NULL, entry.path, + GIT_FILTER_TO_ODB, GIT_FILTER_ALLOW_UNSAFE))) + { + int fd = git_futils_open_ro(full_path.ptr); + if (fd < 0) + error = fd; + else { + error = git_odb__hashfd_filtered( + out, fd, (size_t)entry.file_size, GIT_OBJ_BLOB, fl); + p_close(fd); + diff->base.perf.oid_calculations++; + } + + git_filter_list_free(fl); + } + + /* update index for entry if requested */ + if (!error && update_match && git_oid_equal(out, update_match)) { + git_index *idx; + git_index_entry updated_entry; + + memcpy(&updated_entry, &entry, sizeof(git_index_entry)); + updated_entry.mode = mode; + git_oid_cpy(&updated_entry.id, out); + + if (!(error = git_repository_index__weakptr(&idx, + diff->base.repo))) { + error = git_index_add(idx, &updated_entry); + diff->index_updated = true; + } + } + + git_buf_free(&full_path); + return error; +} + +typedef struct { + git_repository *repo; + git_iterator *old_iter; + git_iterator *new_iter; + const git_index_entry *oitem; + const git_index_entry *nitem; +} diff_in_progress; + +#define MODE_BITS_MASK 0000777 + +static int maybe_modified_submodule( + git_delta_t *status, + git_oid *found_oid, + git_diff_generated *diff, + diff_in_progress *info) +{ + int error = 0; + git_submodule *sub; + unsigned int sm_status = 0; + git_submodule_ignore_t ign = diff->base.opts.ignore_submodules; + + *status = GIT_DELTA_UNMODIFIED; + + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES) || + ign == GIT_SUBMODULE_IGNORE_ALL) + return 0; + + if ((error = git_submodule_lookup( + &sub, diff->base.repo, info->nitem->path)) < 0) { + + /* GIT_EEXISTS means dir with .git in it was found - ignore it */ + if (error == GIT_EEXISTS) { + giterr_clear(); + error = 0; + } + return error; + } + + if (ign <= 0 && git_submodule_ignore(sub) == GIT_SUBMODULE_IGNORE_ALL) + /* ignore it */; + else if ((error = git_submodule__status( + &sm_status, NULL, NULL, found_oid, sub, ign)) < 0) + /* return error below */; + + /* check IS_WD_UNMODIFIED because this case is only used + * when the new side of the diff is the working directory + */ + else if (!GIT_SUBMODULE_STATUS_IS_WD_UNMODIFIED(sm_status)) + *status = GIT_DELTA_MODIFIED; + + /* now that we have a HEAD OID, check if HEAD moved */ + else if ((sm_status & GIT_SUBMODULE_STATUS_IN_WD) != 0 && + !git_oid_equal(&info->oitem->id, found_oid)) + *status = GIT_DELTA_MODIFIED; + + git_submodule_free(sub); + return error; +} + +static int maybe_modified( + git_diff_generated *diff, + diff_in_progress *info) +{ + git_oid noid; + git_delta_t status = GIT_DELTA_MODIFIED; + const git_index_entry *oitem = info->oitem; + const git_index_entry *nitem = info->nitem; + unsigned int omode = oitem->mode; + unsigned int nmode = nitem->mode; + bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); + bool modified_uncertain = false; + const char *matched_pathspec; + int error = 0; + + if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) + return 0; + + memset(&noid, 0, sizeof(noid)); + + /* on platforms with no symlinks, preserve mode of existing symlinks */ + if (S_ISLNK(omode) && S_ISREG(nmode) && new_is_workdir && + !(diff->diffcaps & GIT_DIFFCAPS_HAS_SYMLINKS)) + nmode = omode; + + /* on platforms with no execmode, just preserve old mode */ + if (!(diff->diffcaps & GIT_DIFFCAPS_TRUST_MODE_BITS) && + (nmode & MODE_BITS_MASK) != (omode & MODE_BITS_MASK) && + new_is_workdir) + nmode = (nmode & ~MODE_BITS_MASK) | (omode & MODE_BITS_MASK); + + /* if one side is a conflict, mark the whole delta as conflicted */ + if (git_index_entry_is_conflict(oitem) || + git_index_entry_is_conflict(nitem)) { + status = GIT_DELTA_CONFLICTED; + + /* support "assume unchanged" (poorly, b/c we still stat everything) */ + } else if ((oitem->flags & GIT_IDXENTRY_VALID) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* support "skip worktree" index bit */ + } else if ((oitem->flags_extended & GIT_IDXENTRY_SKIP_WORKTREE) != 0) { + status = GIT_DELTA_UNMODIFIED; + + /* if basic type of file changed, then split into delete and add */ + } else if (GIT_MODE_TYPE(omode) != GIT_MODE_TYPE(nmode)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE)) { + status = GIT_DELTA_TYPECHANGE; + } + + else if (nmode == GIT_FILEMODE_UNREADABLE) { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_UNREADABLE, NULL, nitem); + return error; + } + + else { + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + return error; + } + + /* if oids and modes match (and are valid), then file is unmodified */ + } else if (git_oid_equal(&oitem->id, &nitem->id) && + omode == nmode && + !git_oid_iszero(&oitem->id)) { + status = GIT_DELTA_UNMODIFIED; + + /* if we have an unknown OID and a workdir iterator, then check some + * circumstances that can accelerate things or need special handling + */ + } else if (git_oid_iszero(&nitem->id) && new_is_workdir) { + bool use_ctime = + ((diff->diffcaps & GIT_DIFFCAPS_TRUST_CTIME) != 0); + git_index *index = git_iterator_index(info->new_iter); + + status = GIT_DELTA_UNMODIFIED; + + if (S_ISGITLINK(nmode)) { + if ((error = maybe_modified_submodule(&status, &noid, diff, info)) < 0) + return error; + } + + /* if the stat data looks different, then mark modified - this just + * means that the OID will be recalculated below to confirm change + */ + else if (omode != nmode || oitem->file_size != nitem->file_size) { + status = GIT_DELTA_MODIFIED; + modified_uncertain = + (oitem->file_size <= 0 && nitem->file_size > 0); + } + else if (!git_index_time_eq(&oitem->mtime, &nitem->mtime) || + (use_ctime && !git_index_time_eq(&oitem->ctime, &nitem->ctime)) || + oitem->ino != nitem->ino || + oitem->uid != nitem->uid || + oitem->gid != nitem->gid || + git_index_entry_newer_than_index(nitem, index)) + { + status = GIT_DELTA_MODIFIED; + modified_uncertain = true; + } + + /* if mode is GITLINK and submodules are ignored, then skip */ + } else if (S_ISGITLINK(nmode) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) { + status = GIT_DELTA_UNMODIFIED; + } + + /* if we got here and decided that the files are modified, but we + * haven't calculated the OID of the new item, then calculate it now + */ + if (modified_uncertain && git_oid_iszero(&nitem->id)) { + const git_oid *update_check = + DIFF_FLAG_IS_SET(diff, GIT_DIFF_UPDATE_INDEX) && omode == nmode ? + &oitem->id : NULL; + + if ((error = git_diff__oid_for_entry( + &noid, &diff->base, nitem, nmode, update_check)) < 0) + return error; + + /* if oid matches, then mark unmodified (except submodules, where + * the filesystem content may be modified even if the oid still + * matches between the index and the workdir HEAD) + */ + if (omode == nmode && !S_ISGITLINK(omode) && + git_oid_equal(&oitem->id, &noid)) + status = GIT_DELTA_UNMODIFIED; + } + + /* If we want case changes, then break this into a delete of the old + * and an add of the new so that consumers can act accordingly (eg, + * checkout will update the case on disk.) + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE) && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_CASECHANGE) && + strcmp(oitem->path, nitem->path) != 0) { + + if (!(error = diff_delta__from_one(diff, GIT_DELTA_DELETED, oitem, NULL))) + error = diff_delta__from_one(diff, GIT_DELTA_ADDED, NULL, nitem); + + return error; + } + + return diff_delta__from_two( + diff, status, oitem, omode, nitem, nmode, + git_oid_iszero(&noid) ? NULL : &noid, matched_pathspec); +} + +static bool entry_is_prefixed( + git_diff_generated *diff, + const git_index_entry *item, + const git_index_entry *prefix_item) +{ + size_t pathlen; + + if (!item || diff->base.pfxcomp(item->path, prefix_item->path) != 0) + return false; + + pathlen = strlen(prefix_item->path); + + return (prefix_item->path[pathlen - 1] == '/' || + item->path[pathlen] == '\0' || + item->path[pathlen] == '/'); +} + +static int iterator_current( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_current(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance( + const git_index_entry **entry, + git_iterator *iterator) +{ + const git_index_entry *prev_entry = *entry; + int cmp, error; + + /* if we're looking for conflicts, we only want to report + * one conflict for each file, instead of all three sides. + * so if this entry is a conflict for this file, and the + * previous one was a conflict for the same file, skip it. + */ + while ((error = git_iterator_advance(entry, iterator)) == 0) { + if (!(iterator->flags & GIT_ITERATOR_INCLUDE_CONFLICTS) || + !git_index_entry_is_conflict(prev_entry) || + !git_index_entry_is_conflict(*entry)) + break; + + cmp = (iterator->flags & GIT_ITERATOR_IGNORE_CASE) ? + strcasecmp(prev_entry->path, (*entry)->path) : + strcmp(prev_entry->path, (*entry)->path); + + if (cmp) + break; + } + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_into( + const git_index_entry **entry, + git_iterator *iterator) +{ + int error; + + if ((error = git_iterator_advance_into(entry, iterator)) == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iterator) +{ + int error = git_iterator_advance_over(entry, status, iterator); + + if (error == GIT_ITEROVER) { + *entry = NULL; + error = 0; + } + + return error; +} + +static int handle_unmatched_new_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + const git_index_entry *nitem = info->nitem; + git_delta_t delta_type = GIT_DELTA_UNTRACKED; + bool contains_oitem; + + /* check if this is a prefix of the other side */ + contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(nitem)) + delta_type = GIT_DELTA_CONFLICTED; + + /* update delta_type if this item is ignored */ + else if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; + + if (nitem->mode == GIT_FILEMODE_TREE) { + bool recurse_into_dir = contains_oitem; + + /* check if user requests recursion into this type of dir */ + recurse_into_dir = contains_oitem || + (delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) || + (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)); + + /* do not advance into directories that contain a .git file */ + if (recurse_into_dir && !contains_oitem) { + git_buf *full = NULL; + if (git_iterator_current_workdir_path(&full, info->new_iter) < 0) + return -1; + if (full && git_path_contains(full, DOT_GIT)) { + /* TODO: warning if not a valid git repository */ + recurse_into_dir = false; + } + } + + /* still have to look into untracked directories to match core git - + * with no untracked files, directory is treated as ignored + */ + if (!recurse_into_dir && + delta_type == GIT_DELTA_UNTRACKED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS)) + { + git_diff_delta *last; + git_iterator_status_t untracked_state; + + /* attempt to insert record for this directory */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* if delta wasn't created (because of rules), just skip ahead */ + last = diff_delta__last_for_item(diff, nitem); + if (!last) + return iterator_advance(&info->nitem, info->new_iter); + + /* iterate into dir looking for an actual untracked file */ + if ((error = iterator_advance_over( + &info->nitem, &untracked_state, info->new_iter)) < 0) + return error; + + /* if we found nothing that matched our pathlist filter, exclude */ + if (untracked_state == GIT_ITERATOR_STATUS_FILTERED) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + + /* if we found nothing or just ignored items, update the record */ + if (untracked_state == GIT_ITERATOR_STATUS_IGNORED || + untracked_state == GIT_ITERATOR_STATUS_EMPTY) { + last->status = GIT_DELTA_IGNORED; + + /* remove the record if we don't want ignored records */ + if (DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_IGNORED)) { + git_vector_pop(&diff->base.deltas); + git__free(last); + } + } + + return 0; + } + + /* try to advance into directory if necessary */ + if (recurse_into_dir) { + error = iterator_advance_into(&info->nitem, info->new_iter); + + /* if directory is empty, can't advance into it, so skip it */ + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = iterator_advance(&info->nitem, info->new_iter); + } + + return error; + } + } + + else if (delta_type == GIT_DELTA_IGNORED && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) + /* item contained in ignored directory, so skip over it */ + return iterator_advance(&info->nitem, info->new_iter); + + else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) { + if (delta_type != GIT_DELTA_CONFLICTED) + delta_type = GIT_DELTA_ADDED; + } + + else if (nitem->mode == GIT_FILEMODE_COMMIT) { + /* ignore things that are not actual submodules */ + if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) { + giterr_clear(); + delta_type = GIT_DELTA_IGNORED; + + /* if this contains a tracked item, treat as normal TREE */ + if (contains_oitem) { + error = iterator_advance_into(&info->nitem, info->new_iter); + if (error != GIT_ENOTFOUND) + return error; + + giterr_clear(); + return iterator_advance(&info->nitem, info->new_iter); + } + } + } + + else if (nitem->mode == GIT_FILEMODE_UNREADABLE) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED)) + delta_type = GIT_DELTA_UNTRACKED; + else + delta_type = GIT_DELTA_UNREADABLE; + } + + /* Actually create the record for this item if necessary */ + if ((error = diff_delta__from_one(diff, delta_type, NULL, nitem)) != 0) + return error; + + /* If user requested TYPECHANGE records, then check for that instead of + * just generating an ADDED/UNTRACKED record + */ + if (delta_type != GIT_DELTA_IGNORED && + DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + contains_oitem) + { + /* this entry was prefixed with a tree - make TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, nitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->old_file.mode = GIT_FILEMODE_TREE; + } + } + + return iterator_advance(&info->nitem, info->new_iter); +} + +static int handle_unmatched_old_item( + git_diff_generated *diff, diff_in_progress *info) +{ + git_delta_t delta_type = GIT_DELTA_DELETED; + int error; + + /* update delta_type if this item is conflicted */ + if (git_index_entry_is_conflict(info->oitem)) + delta_type = GIT_DELTA_CONFLICTED; + + if ((error = diff_delta__from_one(diff, delta_type, info->oitem, NULL)) < 0) + return error; + + /* if we are generating TYPECHANGE records then check for that + * instead of just generating a DELETE record + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_INCLUDE_TYPECHANGE_TREES) && + entry_is_prefixed(diff, info->nitem, info->oitem)) + { + /* this entry has become a tree! convert to TYPECHANGE */ + git_diff_delta *last = diff_delta__last_for_item(diff, info->oitem); + if (last) { + last->status = GIT_DELTA_TYPECHANGE; + last->new_file.mode = GIT_FILEMODE_TREE; + } + + /* If new_iter is a workdir iterator, then this situation + * will certainly be followed by a series of untracked items. + * Unless RECURSE_UNTRACKED_DIRS is set, skip over them... + */ + if (S_ISDIR(info->nitem->mode) && + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_UNTRACKED_DIRS)) + return iterator_advance(&info->nitem, info->new_iter); + } + + return iterator_advance(&info->oitem, info->old_iter); +} + +static int handle_matched_item( + git_diff_generated *diff, diff_in_progress *info) +{ + int error = 0; + + if ((error = maybe_modified(diff, info)) < 0) + return error; + + if (!(error = iterator_advance(&info->oitem, info->old_iter))) + error = iterator_advance(&info->nitem, info->new_iter); + + return error; +} + +int git_diff__from_iterators( + git_diff **out, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts) +{ + git_diff_generated *diff; + diff_in_progress info; + int error = 0; + + *out = NULL; + + diff = diff_generated_alloc(repo, old_iter, new_iter); + GITERR_CHECK_ALLOC(diff); + + info.repo = repo; + info.old_iter = old_iter; + info.new_iter = new_iter; + + /* make iterators have matching icase behavior */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { + git_iterator_set_ignore_case(old_iter, true); + git_iterator_set_ignore_case(new_iter, true); + } + + /* finish initialization */ + if ((error = diff_generated_apply_options(diff, opts)) < 0) + goto cleanup; + + if ((error = iterator_current(&info.oitem, old_iter)) < 0 || + (error = iterator_current(&info.nitem, new_iter)) < 0) + goto cleanup; + + /* run iterators building diffs */ + while (!error && (info.oitem || info.nitem)) { + int cmp; + + /* report progress */ + if (opts && opts->progress_cb) { + if ((error = opts->progress_cb(&diff->base, + info.oitem ? info.oitem->path : NULL, + info.nitem ? info.nitem->path : NULL, + opts->payload))) + break; + } + + cmp = info.oitem ? + (info.nitem ? diff->base.entrycomp(info.oitem, info.nitem) : -1) : 1; + + /* create DELETED records for old items not matched in new */ + if (cmp < 0) + error = handle_unmatched_old_item(diff, &info); + + /* create ADDED, TRACKED, or IGNORED records for new items not + * matched in old (and/or descend into directories as needed) + */ + else if (cmp > 0) + error = handle_unmatched_new_item(diff, &info); + + /* otherwise item paths match, so create MODIFIED record + * (or ADDED and DELETED pair if type changed) + */ + else + error = handle_matched_item(diff, &info); + } + + diff->base.perf.stat_calls += + old_iter->stat_calls + new_iter->stat_calls; + +cleanup: + if (!error) + *out = &diff->base; + else + git_diff_free(&diff->base); + + return error; +} + +#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ + git_iterator *a = NULL, *b = NULL; \ + char *pfx = (opts && !(opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) ? \ + git_pathspec_prefix(&opts->pathspec) : NULL; \ + git_iterator_options a_opts = GIT_ITERATOR_OPTIONS_INIT, \ + b_opts = GIT_ITERATOR_OPTIONS_INIT; \ + a_opts.flags = FLAGS_FIRST; \ + a_opts.start = pfx; \ + a_opts.end = pfx; \ + b_opts.flags = FLAGS_SECOND; \ + b_opts.start = pfx; \ + b_opts.end = pfx; \ + GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ + if (opts && (opts->flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { \ + a_opts.pathlist.strings = opts->pathspec.strings; \ + a_opts.pathlist.count = opts->pathspec.count; \ + b_opts.pathlist.strings = opts->pathspec.strings; \ + b_opts.pathlist.count = opts->pathspec.count; \ + } \ + if (!error && !(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + error = git_diff__from_iterators(&diff, repo, a, b, opts); \ + git__free(pfx); git_iterator_free(a); git_iterator_free(b); \ +} while (0) + +int git_diff_tree_to_tree( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + git_tree *new_tree, + const git_diff_options *opts) +{ + git_diff *diff = NULL; + git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; + int error = 0; + + assert(out && repo); + + *out = NULL; + + /* for tree to tree diff, be case sensitive even if the index is + * currently case insensitive, unless the user explicitly asked + * for case insensitivity + */ + if (opts && (opts->flags & GIT_DIFF_IGNORE_CASE) != 0) + iflag = GIT_ITERATOR_IGNORE_CASE; + + DIFF_FROM_ITERATORS( + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_tree(&b, new_tree, &b_opts), iflag + ); + + if (!error) + *out = diff; + + return error; +} + +static int diff_load_index(git_index **index, git_repository *repo) +{ + int error = git_repository_index__weakptr(index, repo); + + /* reload the repository index when user did not pass one in */ + if (!error && git_index_read(*index, false) < 0) + giterr_clear(); + + return error; +} + +int git_diff_tree_to_index( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + git_index *index, + const git_diff_options *opts) +{ + git_diff *diff = NULL; + git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + bool index_ignore_case = false; + int error = 0; + + assert(out && repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + index_ignore_case = index->ignore_case; + + DIFF_FROM_ITERATORS( + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_index(&b, repo, index, &b_opts), iflag + ); + + /* if index is in case-insensitive order, re-sort deltas to match */ + if (!error && index_ignore_case) + git_diff__set_ignore_case(diff, true); + + if (!error) + *out = diff; + + return error; +} + +int git_diff_index_to_workdir( + git_diff **out, + git_repository *repo, + git_index *index, + const git_diff_options *opts) +{ + git_diff *diff = NULL; + int error = 0; + + assert(out && repo); + + *out = NULL; + + if (!index && (error = diff_load_index(&index, repo)) < 0) + return error; + + DIFF_FROM_ITERATORS( + git_iterator_for_index(&a, repo, index, &a_opts), + GIT_ITERATOR_INCLUDE_CONFLICTS, + + git_iterator_for_workdir(&b, repo, index, NULL, &b_opts), + GIT_ITERATOR_DONT_AUTOEXPAND + ); + + if (!error && (diff->opts.flags & GIT_DIFF_UPDATE_INDEX) != 0 && + ((git_diff_generated *)diff)->index_updated) + error = git_index_write(index); + + if (!error) + *out = diff; + + return error; +} + +int git_diff_tree_to_workdir( + git_diff **out, + git_repository *repo, + git_tree *old_tree, + const git_diff_options *opts) +{ + git_diff *diff = NULL; + git_index *index; + int error = 0; + + assert(out && repo); + + *out = NULL; + + if ((error = git_repository_index__weakptr(&index, repo))) + return error; + + DIFF_FROM_ITERATORS( + git_iterator_for_tree(&a, old_tree, &a_opts), 0, + git_iterator_for_workdir(&b, repo, index, old_tree, &b_opts), GIT_ITERATOR_DONT_AUTOEXPAND + ); + + if (!error) + *out = diff; + + return error; +} + +int git_diff_tree_to_workdir_with_index( + git_diff **out, + git_repository *repo, + git_tree *tree, + const git_diff_options *opts) +{ + git_diff *d1 = NULL, *d2 = NULL; + git_index *index = NULL; + int error = 0; + + assert(out && repo); + + *out = NULL; + + if ((error = diff_load_index(&index, repo)) < 0) + return error; + + if (!(error = git_diff_tree_to_index(&d1, repo, tree, index, opts)) && + !(error = git_diff_index_to_workdir(&d2, repo, index, opts))) + error = git_diff_merge(d1, d2); + + git_diff_free(d2); + + if (error) { + git_diff_free(d1); + d1 = NULL; + } + + *out = d1; + return error; +} + +int git_diff_index_to_index( + git_diff **out, + git_repository *repo, + git_index *old_index, + git_index *new_index, + const git_diff_options *opts) +{ + git_diff *diff; + int error = 0; + + assert(out && old_index && new_index); + + *out = NULL; + + DIFF_FROM_ITERATORS( + git_iterator_for_index(&a, repo, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE, + git_iterator_for_index(&b, repo, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE + ); + + /* if index is in case-insensitive order, re-sort deltas to match */ + if (!error && (old_index->ignore_case || new_index->ignore_case)) + git_diff__set_ignore_case(diff, true); + + if (!error) + *out = diff; + + return error; +} + +int git_diff__paired_foreach( + git_diff *head2idx, + git_diff *idx2wd, + int (*cb)(git_diff_delta *h2i, git_diff_delta *i2w, void *payload), + void *payload) +{ + int cmp, error = 0; + git_diff_delta *h2i, *i2w; + size_t i, j, i_max, j_max; + int (*strcomp)(const char *, const char *) = git__strcmp; + bool h2i_icase, i2w_icase, icase_mismatch; + + i_max = head2idx ? head2idx->deltas.length : 0; + j_max = idx2wd ? idx2wd->deltas.length : 0; + if (!i_max && !j_max) + return 0; + + /* At some point, tree-to-index diffs will probably never ignore case, + * even if that isn't true now. Index-to-workdir diffs may or may not + * ignore case, but the index filename for the idx2wd diff should + * still be using the canonical case-preserving name. + * + * Therefore the main thing we need to do here is make sure the diffs + * are traversed in a compatible order. To do this, we temporarily + * resort a mismatched diff to get the order correct. + * + * In order to traverse renames in the index->workdir, we need to + * ensure that we compare the index name on both sides, so we + * always sort by the old name in the i2w list. + */ + h2i_icase = head2idx != NULL && git_diff_is_sorted_icase(head2idx); + i2w_icase = idx2wd != NULL && git_diff_is_sorted_icase(idx2wd); + + icase_mismatch = + (head2idx != NULL && idx2wd != NULL && h2i_icase != i2w_icase); + + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__cmp); + git_vector_sort(&head2idx->deltas); + } + + if (i2w_icase && !icase_mismatch) { + strcomp = git__strcasecmp; + + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_casecmp); + git_vector_sort(&idx2wd->deltas); + } else if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, git_diff_delta__i2w_cmp); + git_vector_sort(&idx2wd->deltas); + } + + for (i = 0, j = 0; i < i_max || j < j_max; ) { + h2i = head2idx ? GIT_VECTOR_GET(&head2idx->deltas, i) : NULL; + i2w = idx2wd ? GIT_VECTOR_GET(&idx2wd->deltas, j) : NULL; + + cmp = !i2w ? -1 : !h2i ? 1 : + strcomp(h2i->new_file.path, i2w->old_file.path); + + if (cmp < 0) { + i++; i2w = NULL; + } else if (cmp > 0) { + j++; h2i = NULL; + } else { + i++; j++; + } + + if ((error = cb(h2i, i2w, payload)) != 0) { + giterr_set_after_callback(error); + break; + } + } + + /* restore case-insensitive delta sort */ + if (icase_mismatch && h2i_icase) { + git_vector_set_cmp(&head2idx->deltas, git_diff_delta__casecmp); + git_vector_sort(&head2idx->deltas); + } + + /* restore idx2wd sort by new path */ + if (idx2wd != NULL) { + git_vector_set_cmp(&idx2wd->deltas, + i2w_icase ? git_diff_delta__casecmp : git_diff_delta__cmp); + git_vector_sort(&idx2wd->deltas); + } + + return error; +} + +int git_diff__commit( + git_diff **out, + git_repository *repo, + const git_commit *commit, + const git_diff_options *opts) +{ + git_commit *parent = NULL; + git_diff *commit_diff = NULL; + git_tree *old_tree = NULL, *new_tree = NULL; + size_t parents; + int error = 0; + + *out = NULL; + + if ((parents = git_commit_parentcount(commit)) > 1) { + char commit_oidstr[GIT_OID_HEXSZ + 1]; + + error = -1; + giterr_set(GITERR_INVALID, "Commit %s is a merge commit", + git_oid_tostr(commit_oidstr, GIT_OID_HEXSZ + 1, git_commit_id(commit))); + goto on_error; + } + + if (parents > 0) + if ((error = git_commit_parent(&parent, commit, 0)) < 0 || + (error = git_commit_tree(&old_tree, parent)) < 0) + goto on_error; + + if ((error = git_commit_tree(&new_tree, commit)) < 0 || + (error = git_diff_tree_to_tree(&commit_diff, repo, old_tree, new_tree, opts)) < 0) + goto on_error; + + *out = commit_diff; + +on_error: + git_tree_free(new_tree); + git_tree_free(old_tree); + git_commit_free(parent); + + return error; +} + diff --git a/src/diff_generate.h b/src/diff_generate.h new file mode 100644 index 000000000..63e7f0532 --- /dev/null +++ b/src/diff_generate.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_generate_h__ +#define INCLUDE_diff_generate_h__ + +enum { + GIT_DIFFCAPS_HAS_SYMLINKS = (1 << 0), /* symlinks on platform? */ + GIT_DIFFCAPS_IGNORE_STAT = (1 << 1), /* use stat? */ + GIT_DIFFCAPS_TRUST_MODE_BITS = (1 << 2), /* use st_mode? */ + GIT_DIFFCAPS_TRUST_CTIME = (1 << 3), /* use st_ctime? */ + GIT_DIFFCAPS_USE_DEV = (1 << 4), /* use st_dev? */ +}; + +#define DIFF_FLAGS_KNOWN_BINARY (GIT_DIFF_FLAG_BINARY|GIT_DIFF_FLAG_NOT_BINARY) +#define DIFF_FLAGS_NOT_BINARY (GIT_DIFF_FLAG_NOT_BINARY|GIT_DIFF_FLAG__NO_DATA) + +enum { + GIT_DIFF_FLAG__FREE_PATH = (1 << 7), /* `path` is allocated memory */ + GIT_DIFF_FLAG__FREE_DATA = (1 << 8), /* internal file data is allocated */ + GIT_DIFF_FLAG__UNMAP_DATA = (1 << 9), /* internal file data is mmap'ed */ + GIT_DIFF_FLAG__NO_DATA = (1 << 10), /* file data should not be loaded */ + GIT_DIFF_FLAG__FREE_BLOB = (1 << 11), /* release the blob when done */ + GIT_DIFF_FLAG__LOADED = (1 << 12), /* file data has been loaded */ + + GIT_DIFF_FLAG__TO_DELETE = (1 << 16), /* delete entry during rename det. */ + GIT_DIFF_FLAG__TO_SPLIT = (1 << 17), /* split entry during rename det. */ + GIT_DIFF_FLAG__IS_RENAME_TARGET = (1 << 18), + GIT_DIFF_FLAG__IS_RENAME_SOURCE = (1 << 19), + GIT_DIFF_FLAG__HAS_SELF_SIMILARITY = (1 << 20), +}; + +#define GIT_DIFF_FLAG__CLEAR_INTERNAL(F) (F) = ((F) & 0x00FFFF) + +#define GIT_DIFF__VERBOSE (1 << 30) + +extern void git_diff_addref(git_diff *diff); + +extern bool git_diff_delta__should_skip( + const git_diff_options *opts, const git_diff_delta *delta); + +extern int git_diff__from_iterators( + git_diff **diff_ptr, + git_repository *repo, + git_iterator *old_iter, + git_iterator *new_iter, + const git_diff_options *opts); + +extern int git_diff__commit( + git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); + +extern int git_diff__paired_foreach( + git_diff *idx2head, + git_diff *wd2idx, + int (*cb)(git_diff_delta *i2h, git_diff_delta *w2i, void *payload), + void *payload); + +/* Merge two `git_diff`s according to the callback given by `cb`. */ + +typedef git_diff_delta *(*git_diff__merge_cb)( + const git_diff_delta *left, + const git_diff_delta *right, + git_pool *pool); + +extern int git_diff__merge( + git_diff *onto, const git_diff *from, git_diff__merge_cb cb); + +extern git_diff_delta *git_diff__merge_like_cgit( + const git_diff_delta *a, + const git_diff_delta *b, + git_pool *pool); + +/* Duplicate a `git_diff_delta` out of the `git_pool` */ +extern git_diff_delta *git_diff__delta_dup( + const git_diff_delta *d, git_pool *pool); + +extern int git_diff__oid_for_file( + git_oid *out, + git_diff *diff, + const char *path, + uint16_t mode, + git_off_t size); + +extern int git_diff__oid_for_entry( + git_oid *out, + git_diff *diff, + const git_index_entry *src, + uint16_t mode, + const git_oid *update_match); + +/* + * Sometimes a git_diff_file will have a zero size; this attempts to + * fill in the size without loading the blob if possible. If that is + * not possible, then it will return the git_odb_object that had to be + * loaded and the caller can use it or dispose of it as needed. + */ +GIT_INLINE(int) git_diff_file__resolve_zero_size( + git_diff_file *file, git_odb_object **odb_obj, git_repository *repo) +{ + int error; + git_odb *odb; + size_t len; + git_otype type; + + if ((error = git_repository_odb(&odb, repo)) < 0) + return error; + + error = git_odb__read_header_or_object( + odb_obj, &len, &type, odb, &file->id); + + git_odb_free(odb); + + if (!error) + file->size = (git_off_t)len; + + return error; +} + +#endif + diff --git a/src/diff_parse.c b/src/diff_parse.c new file mode 100644 index 000000000..e640063af --- /dev/null +++ b/src/diff_parse.c @@ -0,0 +1,101 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "common.h" +#include "diff.h" +#include "diff_parse.h" +#include "patch.h" +#include "patch_parse.h" + +static void diff_parsed_free(git_diff *d) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *patch; + size_t i; + + git_vector_foreach(&diff->patches, i, patch) + git_patch_free(patch); + + git_vector_free(&diff->patches); + + git_vector_free(&diff->base.deltas); + git_pool_clear(&diff->base.pool); + + git__memzero(diff, sizeof(*diff)); + git__free(diff); +} + +static git_diff_parsed *diff_parsed_alloc(void) +{ + git_diff_parsed *diff; + + if ((diff = git__calloc(1, sizeof(git_diff_parsed))) == NULL) + return NULL; + + GIT_REFCOUNT_INC(diff); + diff->base.type = GIT_DIFF_TYPE_PARSED; + diff->base.opts.flags &= ~GIT_DIFF_IGNORE_CASE; + diff->base.strcomp = git__strcmp; + diff->base.strncomp = git__strncmp; + diff->base.pfxcomp = git__prefixcmp; + diff->base.entrycomp = git_diff__entry_cmp; + diff->base.patch_fn = git_patch_parsed_from_diff; + diff->base.free_fn = diff_parsed_free; + + git_pool_init(&diff->base.pool, 1); + + if (git_vector_init(&diff->patches, 0, NULL) < 0 || + git_vector_init(&diff->base.deltas, 0, git_diff_delta__cmp) < 0) { + git_diff_free(&diff->base); + return NULL; + } + + git_vector_set_cmp(&diff->base.deltas, git_diff_delta__cmp); + + return diff; +} + +int git_diff_from_buffer( + git_diff **out, + const char *content, + size_t content_len) +{ + git_diff_parsed *diff; + git_patch *patch; + git_patch_parse_ctx *ctx = NULL; + int error = 0; + + *out = NULL; + + diff = diff_parsed_alloc(); + GITERR_CHECK_ALLOC(diff); + + ctx = git_patch_parse_ctx_init(content, content_len, NULL); + GITERR_CHECK_ALLOC(ctx); + + while (ctx->remain_len) { + if ((error = git_patch_parse(&patch, ctx)) < 0) + break; + + git_vector_insert(&diff->patches, patch); + git_vector_insert(&diff->base.deltas, patch->delta); + } + + if (error == GIT_ENOTFOUND && git_vector_length(&diff->patches) > 0) { + giterr_clear(); + error = 0; + } + + git_patch_parse_ctx_free(ctx); + + if (error < 0) + git_diff_free(&diff->base); + else + *out = &diff->base; + + return error; +} + diff --git a/src/diff_parse.h b/src/diff_parse.h new file mode 100644 index 000000000..c47d4cbc9 --- /dev/null +++ b/src/diff_parse.h @@ -0,0 +1,18 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_parse_h__ +#define INCLUDE_diff_parse_h__ + +#include "diff.h" + +typedef struct { + struct git_diff base; + + git_vector patches; +} git_diff_parsed; + +#endif diff --git a/src/diff_patch.h b/src/diff_patch.h deleted file mode 100644 index 7b4dacdde..000000000 --- a/src/diff_patch.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) the libgit2 contributors. All rights reserved. - * - * This file is part of libgit2, distributed under the GNU GPL v2 with - * a Linking Exception. For full terms see the included COPYING file. - */ -#ifndef INCLUDE_diff_patch_h__ -#define INCLUDE_diff_patch_h__ - -#include "common.h" -#include "diff.h" -#include "diff_file.h" -#include "array.h" -#include "git2/patch.h" - - /* cached information about a hunk in a diff */ -typedef struct diff_patch_hunk { - git_diff_hunk hunk; - size_t line_start; - size_t line_count; -} diff_patch_hunk; - -enum { - GIT_DIFF_PATCH_ALLOCATED = (1 << 0), - GIT_DIFF_PATCH_INITIALIZED = (1 << 1), - GIT_DIFF_PATCH_LOADED = (1 << 2), - /* the two sides are different */ - GIT_DIFF_PATCH_DIFFABLE = (1 << 3), - /* the difference between the two sides has been computed */ - GIT_DIFF_PATCH_DIFFED = (1 << 4), - GIT_DIFF_PATCH_FLATTENED = (1 << 5), -}; - -struct git_patch { - git_refcount rc; - git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ - git_diff_options diff_opts; - git_diff_delta *delta; - size_t delta_index; - git_diff_file_content ofile; - git_diff_file_content nfile; - uint32_t flags; - git_diff_binary binary; - git_array_t(diff_patch_hunk) hunks; - git_array_t(git_diff_line) lines; - size_t content_size, context_size, header_size; - git_pool flattened; -}; - -extern git_diff *git_patch__diff(git_patch *); - -extern git_diff_driver *git_patch__driver(git_patch *); - -extern void git_patch__old_data(char **, size_t *, git_patch *); -extern void git_patch__new_data(char **, size_t *, git_patch *); - -extern int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload); - -typedef struct git_diff_output git_diff_output; -struct git_diff_output { - /* these callbacks are issued with the diff data */ - git_diff_file_cb file_cb; - git_diff_binary_cb binary_cb; - git_diff_hunk_cb hunk_cb; - git_diff_line_cb data_cb; - void *payload; - - /* this records the actual error in cases where it may be obscured */ - int error; - - /* this callback is used to do the diff and drive the other callbacks. - * see diff_xdiff.h for how to use this in practice for now. - */ - int (*diff_cb)(git_diff_output *output, git_patch *patch); -}; - -#endif diff --git a/src/diff_print.c b/src/diff_print.c index dae9e341d..fd1a186c1 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -6,7 +6,8 @@ */ #include "common.h" #include "diff.h" -#include "diff_patch.h" +#include "diff_file.h" +#include "patch_generate.h" #include "fileops.h" #include "zstream.h" #include "blob.h" @@ -14,19 +15,19 @@ #include "git2/sys/diff.h" typedef struct { - git_diff *diff; git_diff_format_t format; git_diff_line_cb print_cb; void *payload; + git_buf *buf; - uint32_t flags; - int oid_strlen; git_diff_line line; - unsigned int - content_loaded : 1, - content_allocated : 1; - git_diff_file_content *ofile; - git_diff_file_content *nfile; + + const char *old_prefix; + const char *new_prefix; + uint32_t flags; + int id_strlen; + + int (*strcomp)(const char *, const char *); } diff_print_info; static int diff_print_info_init__common( @@ -42,17 +43,15 @@ static int diff_print_info_init__common( pi->payload = payload; pi->buf = out; - if (!pi->oid_strlen) { + if (!pi->id_strlen) { if (!repo) - pi->oid_strlen = GIT_ABBREV_DEFAULT; - else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0) + pi->id_strlen = GIT_ABBREV_DEFAULT; + else if (git_repository__cvar(&pi->id_strlen, repo, GIT_CVAR_ABBREV) < 0) return -1; } - pi->oid_strlen += 1; /* for NUL byte */ - - if (pi->oid_strlen > GIT_OID_HEXSZ + 1) - pi->oid_strlen = GIT_OID_HEXSZ + 1; + if (pi->id_strlen > GIT_OID_HEXSZ) + pi->id_strlen = GIT_OID_HEXSZ; memset(&pi->line, 0, sizeof(pi->line)); pi->line.old_lineno = -1; @@ -74,11 +73,13 @@ static int diff_print_info_init_fromdiff( memset(pi, 0, sizeof(diff_print_info)); - pi->diff = diff; - if (diff) { pi->flags = diff->opts.flags; - pi->oid_strlen = diff->opts.id_abbrev; + pi->id_strlen = diff->opts.id_abbrev; + pi->old_prefix = diff->opts.old_prefix; + pi->new_prefix = diff->opts.new_prefix; + + pi->strcomp = diff->strcomp; } return diff_print_info_init__common(pi, out, repo, format, cb, payload); @@ -92,24 +93,16 @@ static int diff_print_info_init_frompatch( git_diff_line_cb cb, void *payload) { - git_repository *repo; - assert(patch); - repo = patch->diff ? patch->diff->repo : NULL; - memset(pi, 0, sizeof(diff_print_info)); - pi->diff = patch->diff; - pi->flags = patch->diff_opts.flags; - pi->oid_strlen = patch->diff_opts.id_abbrev; + pi->id_strlen = patch->diff_opts.id_abbrev; + pi->old_prefix = patch->diff_opts.old_prefix; + pi->new_prefix = patch->diff_opts.new_prefix; - pi->content_loaded = 1; - pi->ofile = &patch->ofile; - pi->nfile = &patch->nfile; - - return diff_print_info_init__common(pi, out, repo, format, cb, payload); + return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload); } static char diff_pick_suffix(int mode) @@ -173,8 +166,8 @@ static int diff_print_one_name_status( diff_print_info *pi = data; git_buf *out = pi->buf; char old_suffix, new_suffix, code = git_diff_status_char(delta->status); - int (*strcomp)(const char *, const char *) = - pi->diff ? pi->diff->strcomp : git__strcmp; + int(*strcomp)(const char *, const char *) = pi->strcomp ? + pi->strcomp : git__strcmp; GIT_UNUSED(progress); @@ -213,6 +206,7 @@ static int diff_print_one_raw( { diff_print_info *pi = data; git_buf *out = pi->buf; + int id_abbrev; char code = git_diff_status_char(delta->status); char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; @@ -223,11 +217,21 @@ static int diff_print_one_raw( git_buf_clear(out); - git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id); - git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id); + id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev : + delta->new_file.id_abbrev; + + if (pi->id_strlen > id_abbrev) { + giterr_set(GITERR_PATCH, + "The patch input contains %d id characters (cannot print %d)", + id_abbrev, pi->id_strlen); + return -1; + } + + git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id); git_buf_printf( - out, (pi->oid_strlen <= GIT_OID_HEXSZ) ? + out, (pi->id_strlen <= GIT_OID_HEXSZ) ? ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c", delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code); @@ -252,53 +256,140 @@ static int diff_print_one_raw( return pi->print_cb(delta, NULL, &pi->line, pi->payload); } +static int diff_print_modes( + git_buf *out, const git_diff_delta *delta) +{ + git_buf_printf(out, "old mode %o\n", delta->old_file.mode); + git_buf_printf(out, "new mode %o\n", delta->new_file.mode); + + return git_buf_oom(out) ? -1 : 0; +} + static int diff_print_oid_range( - git_buf *out, const git_diff_delta *delta, int oid_strlen) + git_buf *out, const git_diff_delta *delta, int id_strlen) { char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1]; - git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id); - git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id); + if (delta->old_file.mode && + id_strlen > delta->old_file.id_abbrev) { + giterr_set(GITERR_PATCH, + "The patch input contains %d id characters (cannot print %d)", + delta->old_file.id_abbrev, id_strlen); + return -1; + } + + if ((delta->new_file.mode && + id_strlen > delta->new_file.id_abbrev)) { + giterr_set(GITERR_PATCH, + "The patch input contains %d id characters (cannot print %d)", + delta->new_file.id_abbrev, id_strlen); + return -1; + } + + git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id); + git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id); - /* TODO: Match git diff more closely */ if (delta->old_file.mode == delta->new_file.mode) { git_buf_printf(out, "index %s..%s %o\n", start_oid, end_oid, delta->old_file.mode); } else { - if (delta->old_file.mode == 0) { + if (delta->old_file.mode == 0) git_buf_printf(out, "new file mode %o\n", delta->new_file.mode); - } else if (delta->new_file.mode == 0) { + else if (delta->new_file.mode == 0) git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode); - } else { - git_buf_printf(out, "old mode %o\n", delta->old_file.mode); - git_buf_printf(out, "new mode %o\n", delta->new_file.mode); - } + else + diff_print_modes(out, delta); + git_buf_printf(out, "index %s..%s\n", start_oid, end_oid); } return git_buf_oom(out) ? -1 : 0; } +static int diff_delta_format_path( + git_buf *out, const char *prefix, const char *filename) +{ + if (git_buf_joinpath(out, prefix, filename) < 0) + return -1; + + return git_buf_quote(out); +} + static int diff_delta_format_with_paths( git_buf *out, const git_diff_delta *delta, - const char *oldpfx, - const char *newpfx, - const char *template) + const char *template, + const char *oldpath, + const char *newpath) { - const char *oldpath = delta->old_file.path; - const char *newpath = delta->new_file.path; - - if (git_oid_iszero(&delta->old_file.id)) { - oldpfx = ""; + if (git_oid_iszero(&delta->old_file.id)) oldpath = "/dev/null"; - } - if (git_oid_iszero(&delta->new_file.id)) { - newpfx = ""; + + if (git_oid_iszero(&delta->new_file.id)) newpath = "/dev/null"; + + return git_buf_printf(out, template, oldpath, newpath); +} + +int diff_delta_format_similarity_header( + git_buf *out, + const git_diff_delta *delta) +{ + git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; + const char *type; + int error = 0; + + if (delta->similarity > 100) { + giterr_set(GITERR_PATCH, "invalid similarity %d", delta->similarity); + error = -1; + goto done; } - return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath); + if (delta->status == GIT_DELTA_RENAMED) + type = "rename"; + else if (delta->status == GIT_DELTA_COPIED) + type = "copy"; + else + abort(); + + if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 || + (error = git_buf_puts(&new_path, delta->new_file.path)) < 0 || + (error = git_buf_quote(&old_path)) < 0 || + (error = git_buf_quote(&new_path)) < 0) + goto done; + + git_buf_printf(out, + "similarity index %d%%\n" + "%s from %s\n" + "%s to %s\n", + delta->similarity, + type, old_path.ptr, + type, new_path.ptr); + + if (git_buf_oom(out)) + error = -1; + +done: + git_buf_free(&old_path); + git_buf_free(&new_path); + + return error; +} + +static bool delta_is_unchanged(const git_diff_delta *delta) +{ + if (git_oid_iszero(&delta->old_file.id) && + git_oid_iszero(&delta->new_file.id)) + return true; + + if (delta->old_file.mode == GIT_FILEMODE_COMMIT || + delta->new_file.mode == GIT_FILEMODE_COMMIT) + return false; + + if (git_oid_equal(&delta->old_file.id, &delta->new_file.id)) + return true; + + return false; } int git_diff_delta__format_file_header( @@ -306,27 +397,56 @@ int git_diff_delta__format_file_header( const git_diff_delta *delta, const char *oldpfx, const char *newpfx, - int oid_strlen) + int id_strlen) { + git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; + bool unchanged = delta_is_unchanged(delta); + int error = 0; + if (!oldpfx) oldpfx = DIFF_OLD_PREFIX_DEFAULT; if (!newpfx) newpfx = DIFF_NEW_PREFIX_DEFAULT; - if (!oid_strlen) - oid_strlen = GIT_ABBREV_DEFAULT + 1; + if (!id_strlen) + id_strlen = GIT_ABBREV_DEFAULT; + + if ((error = diff_delta_format_path( + &old_path, oldpfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path( + &new_path, newpfx, delta->new_file.path)) < 0) + goto done; git_buf_clear(out); - git_buf_printf(out, "diff --git %s%s %s%s\n", - oldpfx, delta->old_file.path, newpfx, delta->new_file.path); + git_buf_printf(out, "diff --git %s %s\n", + old_path.ptr, new_path.ptr); - GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen)); + if (delta->status == GIT_DELTA_RENAMED || + (delta->status == GIT_DELTA_COPIED && unchanged)) { + if ((error = diff_delta_format_similarity_header(out, delta)) < 0) + goto done; + } - if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) - diff_delta_format_with_paths( - out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n"); + if (!unchanged) { + if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0) + goto done; - return git_buf_oom(out) ? -1 : 0; + if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0) + diff_delta_format_with_paths(out, delta, + "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr); + } + + if (unchanged && delta->old_file.mode != delta->new_file.mode) + diff_print_modes(out, delta); + + if (git_buf_oom(out)) + error = -1; + +done: + git_buf_free(&old_path); + git_buf_free(&new_path); + + return error; } static int format_binary( @@ -367,37 +487,29 @@ static int format_binary( return 0; } -static int diff_print_load_content( - diff_print_info *pi, - git_diff_delta *delta) +static int diff_print_patch_file_binary_noshow( + diff_print_info *pi, git_diff_delta *delta, + const char *old_pfx, const char *new_pfx) { - git_diff_file_content *ofile, *nfile; + git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT; int error; - assert(pi->diff); + if ((error = diff_delta_format_path( + &old_path, old_pfx, delta->old_file.path)) < 0 || + (error = diff_delta_format_path( + &new_path, new_pfx, delta->new_file.path)) < 0) + goto done; - ofile = git__calloc(1, sizeof(git_diff_file_content)); - nfile = git__calloc(1, sizeof(git_diff_file_content)); + pi->line.num_lines = 1; + error = diff_delta_format_with_paths( + pi->buf, delta, "Binary files %s and %s differ\n", + old_path.ptr, new_path.ptr); - GITERR_CHECK_ALLOC(ofile); - GITERR_CHECK_ALLOC(nfile); +done: + git_buf_free(&old_path); + git_buf_free(&new_path); - if ((error = git_diff_file_content__init_from_diff( - ofile, pi->diff, delta, true)) < 0 || - (error = git_diff_file_content__init_from_diff( - nfile, pi->diff, delta, true)) < 0) { - - git__free(ofile); - git__free(nfile); - return error; - } - - pi->content_loaded = 1; - pi->content_allocated = 1; - pi->ofile = ofile; - pi->nfile = nfile; - - return 0; + return error; } static int diff_print_patch_file_binary( @@ -408,12 +520,12 @@ static int diff_print_patch_file_binary( size_t pre_binary_size; int error; - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) - goto noshow; + if (delta->status == GIT_DELTA_UNMODIFIED) + return 0; - if (!pi->content_loaded && - (error = diff_print_load_content(pi, delta)) < 0) - return error; + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data) + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); pre_binary_size = pi->buf->size; git_buf_printf(pi->buf, "GIT binary patch\n"); @@ -427,18 +539,14 @@ static int diff_print_patch_file_binary( if (error == GIT_EBUFS) { giterr_clear(); git_buf_truncate(pi->buf, pre_binary_size); - goto noshow; + + return diff_print_patch_file_binary_noshow( + pi, delta, old_pfx, new_pfx); } } pi->line.num_lines++; return error; - -noshow: - pi->line.num_lines = 1; - return diff_delta_format_with_paths( - pi->buf, delta, old_pfx, new_pfx, - "Binary files %s%s and %s%s differ\n"); } static int diff_print_patch_file( @@ -447,15 +555,18 @@ static int diff_print_patch_file( int error; diff_print_info *pi = data; const char *oldpfx = - pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; const char *newpfx = - pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) || (pi->flags & GIT_DIFF_FORCE_BINARY); bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY); - int oid_strlen = binary && show_binary ? - GIT_OID_HEXSZ + 1 : pi->oid_strlen; + int id_strlen = pi->id_strlen; + + if (binary && show_binary) + id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev : + delta->new_file.id_abbrev; GIT_UNUSED(progress); @@ -468,7 +579,7 @@ static int diff_print_patch_file( return 0; if ((error = git_diff_delta__format_file_header( - pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0) + pi->buf, delta, oldpfx, newpfx, id_strlen)) < 0) return error; pi->line.origin = GIT_DIFF_LINE_FILE_HDR; @@ -485,9 +596,9 @@ static int diff_print_patch_binary( { diff_print_info *pi = data; const char *old_pfx = - pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT; + pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT; const char *new_pfx = - pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT; + pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT; int error; git_buf_clear(pi->buf); @@ -582,43 +693,11 @@ int git_diff_print( giterr_set_after_callback_function(error, "git_diff_print"); } - git__free(pi.nfile); - git__free(pi.ofile); - git_buf_free(&buf); return error; } -/* print a git_patch to an output callback */ -int git_patch_print( - git_patch *patch, - git_diff_line_cb print_cb, - void *payload) -{ - int error; - git_buf temp = GIT_BUF_INIT; - diff_print_info pi; - - assert(patch && print_cb); - - if (!(error = diff_print_info_init_frompatch( - &pi, &temp, patch, - GIT_DIFF_FORMAT_PATCH, print_cb, payload))) - { - error = git_patch__invoke_callbacks( - patch, diff_print_patch_file, diff_print_patch_binary, - diff_print_patch_hunk, diff_print_patch_line, &pi); - - if (error) /* make sure error message is set */ - giterr_set_after_callback_function(error, "git_patch_print"); - } - - git_buf_free(&temp); - - return error; -} - int git_diff_print_callback__to_buf( const git_diff_delta *delta, const git_diff_hunk *hunk, @@ -659,6 +738,46 @@ int git_diff_print_callback__to_file_handle( return 0; } +/* print a git_diff to a git_buf */ +int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format) +{ + assert(out && diff); + git_buf_sanitize(out); + return git_diff_print( + diff, format, git_diff_print_callback__to_buf, out); +} + +/* print a git_patch to an output callback */ +int git_patch_print( + git_patch *patch, + git_diff_line_cb print_cb, + void *payload) +{ + int error; + git_buf temp = GIT_BUF_INIT; + diff_print_info pi; + + assert(patch && print_cb); + + if (!(error = diff_print_info_init_frompatch( + &pi, &temp, patch, + GIT_DIFF_FORMAT_PATCH, print_cb, payload))) + { + error = git_patch__invoke_callbacks( + patch, + diff_print_patch_file, diff_print_patch_binary, + diff_print_patch_hunk, diff_print_patch_line, + &pi); + + if (error) /* make sure error message is set */ + giterr_set_after_callback_function(error, "git_patch_print"); + } + + git_buf_free(&temp); + + return error; +} + /* print a git_patch to a git_buf */ int git_patch_to_buf(git_buf *out, git_patch *patch) { diff --git a/src/diff_stats.c b/src/diff_stats.c index 42ccbfb87..2005712cd 100644 --- a/src/diff_stats.c +++ b/src/diff_stats.c @@ -7,7 +7,7 @@ #include "common.h" #include "vector.h" #include "diff.h" -#include "diff_patch.h" +#include "patch_generate.h" #define DIFF_RENAME_FILE_SEPARATOR " => " #define STATS_FULL_MIN_SCALE 7 @@ -190,8 +190,9 @@ int git_diff_get_stats( break; /* keep a count of renames because it will affect formatting */ - delta = git_patch_get_delta(patch); + delta = patch->delta; + /* TODO ugh */ namelen = strlen(delta->new_file.path); if (strcmp(delta->old_file.path, delta->new_file.path) != 0) { namelen += strlen(delta->old_file.path); @@ -299,15 +300,24 @@ int git_diff_stats_to_buf( } if (format & GIT_DIFF_STATS_FULL || format & GIT_DIFF_STATS_SHORT) { - error = git_buf_printf( - out, " %" PRIuZ " file%s changed, %" PRIuZ - " insertion%s(+), %" PRIuZ " deletion%s(-)\n", - stats->files_changed, stats->files_changed != 1 ? "s" : "", - stats->insertions, stats->insertions != 1 ? "s" : "", - stats->deletions, stats->deletions != 1 ? "s" : ""); + git_buf_printf( + out, " %" PRIuZ " file%s changed", + stats->files_changed, stats->files_changed != 1 ? "s" : ""); - if (error < 0) - return error; + if (stats->insertions || stats->deletions == 0) + git_buf_printf( + out, ", %" PRIuZ " insertion%s(+)", + stats->insertions, stats->insertions != 1 ? "s" : ""); + + if (stats->deletions || stats->insertions == 0) + git_buf_printf( + out, ", %" PRIuZ " deletion%s(-)", + stats->deletions, stats->deletions != 1 ? "s" : ""); + + git_buf_putc(out, '\n'); + + if (git_buf_oom(out)) + return -1; } if (format & GIT_DIFF_STATS_INCLUDE_SUMMARY) { @@ -333,4 +343,3 @@ void git_diff_stats_free(git_diff_stats *stats) git__free(stats->filestats); git__free(stats); } - diff --git a/src/diff_tform.c b/src/diff_tform.c index 6a6a62811..e8848bd45 100644 --- a/src/diff_tform.c +++ b/src/diff_tform.c @@ -11,6 +11,7 @@ #include "git2/sys/hashsig.h" #include "diff.h" +#include "diff_generate.h" #include "path.h" #include "fileops.h" #include "config.h" diff --git a/src/diff_tform.h b/src/diff_tform.h new file mode 100644 index 000000000..5bd9712d9 --- /dev/null +++ b/src/diff_tform.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_diff_tform_h__ +#define INCLUDE_diff_tform_h__ + +extern int git_diff_find_similar__hashsig_for_file( + void **out, const git_diff_file *f, const char *path, void *p); + +extern int git_diff_find_similar__hashsig_for_buf( + void **out, const git_diff_file *f, const char *buf, size_t len, void *p); + +extern void git_diff_find_similar__hashsig_free(void *sig, void *payload); + +extern int git_diff_find_similar__calc_similarity( + int *score, void *siga, void *sigb, void *payload); + +#endif + diff --git a/src/diff_xdiff.c b/src/diff_xdiff.c index 1057df3aa..5bd6381b5 100644 --- a/src/diff_xdiff.c +++ b/src/diff_xdiff.c @@ -8,8 +8,8 @@ #include "common.h" #include "diff.h" #include "diff_driver.h" -#include "diff_patch.h" #include "diff_xdiff.h" +#include "patch_generate.h" static int git_xdiff_scan_int(const char **str, int *value) { @@ -56,7 +56,7 @@ fail: typedef struct { git_xdiff_output *xo; - git_patch *patch; + git_patch_generated *patch; git_diff_hunk hunk; int old_lineno, new_lineno; mmfile_t xd_old_data, xd_new_data; @@ -110,9 +110,9 @@ static int diff_update_lines( static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) { git_xdiff_info *info = priv; - git_patch *patch = info->patch; - const git_diff_delta *delta = git_patch_get_delta(patch); - git_diff_output *output = &info->xo->output; + git_patch_generated *patch = info->patch; + const git_diff_delta *delta = patch->base.delta; + git_patch_generated_output *output = &info->xo->output; git_diff_line line; if (len == 1) { @@ -181,7 +181,7 @@ static int git_xdiff_cb(void *priv, mmbuffer_t *bufs, int len) return output->error; } -static int git_xdiff(git_diff_output *output, git_patch *patch) +static int git_xdiff(git_patch_generated_output *output, git_patch_generated *patch) { git_xdiff_output *xo = (git_xdiff_output *)output; git_xdiff_info info; @@ -194,7 +194,7 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) xo->callback.priv = &info; git_diff_find_context_init( - &xo->config.find_func, &findctxt, git_patch__driver(patch)); + &xo->config.find_func, &findctxt, git_patch_generated_driver(patch)); xo->config.find_func_priv = &findctxt; if (xo->config.find_func != NULL) @@ -206,8 +206,8 @@ static int git_xdiff(git_diff_output *output, git_patch *patch) * updates are needed to xo->params.flags */ - git_patch__old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); - git_patch__new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch); + git_patch_generated_old_data(&info.xd_old_data.ptr, &info.xd_old_data.size, patch); + git_patch_generated_new_data(&info.xd_new_data.ptr, &info.xd_new_data.size, patch); if (info.xd_old_data.size > GIT_XDIFF_MAX_SIZE || info.xd_new_data.size > GIT_XDIFF_MAX_SIZE) { diff --git a/src/diff_xdiff.h b/src/diff_xdiff.h index 98e11b2cb..88375986b 100644 --- a/src/diff_xdiff.h +++ b/src/diff_xdiff.h @@ -8,20 +8,20 @@ #define INCLUDE_diff_xdiff_h__ #include "diff.h" -#include "diff_patch.h" #include "xdiff/xdiff.h" +#include "patch_generate.h" /* xdiff cannot cope with large files. these files should not be passed to * xdiff. callers should treat these large files as binary. */ #define GIT_XDIFF_MAX_SIZE (1024LL * 1024 * 1023) -/* A git_xdiff_output is a git_diff_output with extra fields necessary - * to use libxdiff. Calling git_xdiff_init() will set the diff_cb field - * of the output to use xdiff to generate the diffs. +/* A git_xdiff_output is a git_patch_generate_output with extra fields + * necessary to use libxdiff. Calling git_xdiff_init() will set the diff_cb + * field of the output to use xdiff to generate the diffs. */ typedef struct { - git_diff_output output; + git_patch_generated_output output; xdemitconf_t config; xpparam_t params; diff --git a/src/fetchhead.c b/src/fetchhead.c index a95ea4ca4..3d16c2166 100644 --- a/src/fetchhead.c +++ b/src/fetchhead.c @@ -149,7 +149,7 @@ static int fetchhead_ref_parse( if (!*line) { giterr_set(GITERR_FETCHHEAD, - "Empty line in FETCH_HEAD line %d", line_num); + "Empty line in FETCH_HEAD line %"PRIuZ, line_num); return -1; } @@ -163,7 +163,7 @@ static int fetchhead_ref_parse( if (strlen(oid_str) != GIT_OID_HEXSZ) { giterr_set(GITERR_FETCHHEAD, - "Invalid object ID in FETCH_HEAD line %d", line_num); + "Invalid object ID in FETCH_HEAD line %"PRIuZ, line_num); return -1; } @@ -171,7 +171,7 @@ static int fetchhead_ref_parse( const git_error *oid_err = giterr_last(); const char *err_msg = oid_err ? oid_err->message : "Invalid object ID"; - giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %d", + giterr_set(GITERR_FETCHHEAD, "%s in FETCH_HEAD line %"PRIuZ, err_msg, line_num); return -1; } @@ -180,7 +180,7 @@ static int fetchhead_ref_parse( if (*line) { if ((is_merge_str = git__strsep(&line, "\t")) == NULL) { giterr_set(GITERR_FETCHHEAD, - "Invalid description data in FETCH_HEAD line %d", line_num); + "Invalid description data in FETCH_HEAD line %"PRIuZ, line_num); return -1; } @@ -190,13 +190,13 @@ static int fetchhead_ref_parse( *is_merge = 0; else { giterr_set(GITERR_FETCHHEAD, - "Invalid for-merge entry in FETCH_HEAD line %d", line_num); + "Invalid for-merge entry in FETCH_HEAD line %"PRIuZ, line_num); return -1; } if ((desc = line) == NULL) { giterr_set(GITERR_FETCHHEAD, - "Invalid description in FETCH_HEAD line %d", line_num); + "Invalid description in FETCH_HEAD line %"PRIuZ, line_num); return -1; } @@ -213,7 +213,7 @@ static int fetchhead_ref_parse( if ((desc = strstr(name, "' ")) == NULL || git__prefixcmp(desc, "' of ") != 0) { giterr_set(GITERR_FETCHHEAD, - "Invalid description in FETCH_HEAD line %d", line_num); + "Invalid description in FETCH_HEAD line %"PRIuZ, line_num); return -1; } @@ -277,7 +277,7 @@ int git_repository_fetchhead_foreach(git_repository *repo, } if (*buffer) { - giterr_set(GITERR_FETCHHEAD, "No EOL at line %d", line_num+1); + giterr_set(GITERR_FETCHHEAD, "No EOL at line %"PRIuZ, line_num+1); error = -1; goto done; } diff --git a/src/filebuf.c b/src/filebuf.c index 507f6a942..582399470 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -272,6 +272,11 @@ cleanup: } int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) +{ + return git_filebuf_open_withsize(file, path, flags, mode, WRITE_BUFFER_SIZE); +} + +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size) { int compression, error = -1; size_t path_len, alloc_len; @@ -286,7 +291,7 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode if (flags & GIT_FILEBUF_DO_NOT_BUFFER) file->do_not_buffer = true; - file->buf_size = WRITE_BUFFER_SIZE; + file->buf_size = size; file->buf_pos = 0; file->fd = -1; file->last_error = BUFERR_OK; diff --git a/src/filebuf.h b/src/filebuf.h index f4d255b0a..467708d45 100644 --- a/src/filebuf.h +++ b/src/filebuf.h @@ -79,6 +79,7 @@ int git_filebuf_reserve(git_filebuf *file, void **buff, size_t len); int git_filebuf_printf(git_filebuf *file, const char *format, ...) GIT_FORMAT_PRINTF(2, 3); int git_filebuf_open(git_filebuf *lock, const char *path, int flags, mode_t mode); +int git_filebuf_open_withsize(git_filebuf *file, const char *path, int flags, mode_t mode, size_t size); int git_filebuf_commit(git_filebuf *lock); int git_filebuf_commit_at(git_filebuf *lock, const char *path); void git_filebuf_cleanup(git_filebuf *lock); diff --git a/src/fileops.c b/src/fileops.c index 22868b489..a82202c98 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -72,8 +72,16 @@ int git_futils_creat_locked(const char *path, const mode_t mode) O_EXCL | O_BINARY | O_CLOEXEC, mode); if (fd < 0) { + int error = errno; giterr_set(GITERR_OS, "Failed to create locked file '%s'", path); - return errno == EEXIST ? GIT_ELOCKED : -1; + switch (error) { + case EEXIST: + return GIT_ELOCKED; + case ENOENT: + return GIT_ENOTFOUND; + default: + return -1; + } } return fd; @@ -837,6 +845,19 @@ int git_futils_cp(const char *from, const char *to, mode_t filemode) return cp_by_fd(ifd, ofd, true); } +int git_futils_touch(const char *path, time_t *when) +{ + struct p_timeval times[2]; + int ret; + + times[0].tv_sec = times[1].tv_sec = when ? *when : time(NULL); + times[0].tv_usec = times[1].tv_usec = 0; + + ret = p_utimes(path, times); + + return (ret < 0) ? git_path_set_error(errno, path, "touch") : 0; +} + static int cp_link(const char *from, const char *to, size_t link_size) { int error = 0; diff --git a/src/fileops.h b/src/fileops.h index 6c6c49dcf..65c96a6f5 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -45,12 +45,12 @@ extern int git_futils_writebuffer( extern int git_futils_creat_withpath(const char *path, const mode_t dirmode, const mode_t mode); /** - * Create an open a process-locked file + * Create and open a process-locked file */ extern int git_futils_creat_locked(const char *path, const mode_t mode); /** - * Create an open a process-locked file, while + * Create and open a process-locked file, while * also creating all the folders in its path */ extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); @@ -184,6 +184,12 @@ extern int git_futils_cp( const char *to, mode_t filemode); +/** + * Set the files atime and mtime to the given time, or the current time + * if `ts` is NULL. + */ +extern int git_futils_touch(const char *path, time_t *when); + /** * Flags that can be passed to `git_futils_cp_r`. * diff --git a/src/fnmatch.c b/src/fnmatch.c index a2945b8db..33c8a2512 100644 --- a/src/fnmatch.c +++ b/src/fnmatch.c @@ -93,11 +93,24 @@ p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs) * It will be restored if/when we recurse below. */ if (c == '*') { - flags &= ~FNM_PATHNAME; - while (c == '*') - c = *++pattern; - if (c == '/') - c = *++pattern; + c = *++pattern; + /* star-star-slash is at the end, match by default */ + if (c == EOS) + return 0; + /* Double-star must be at end or between slashes */ + if (c != '/') + return (FNM_NOMATCH); + + c = *++pattern; + do { + int e = p_fnmatchx(pattern, string, recurs_flags, recurs); + if (e != FNM_NOMATCH) + return e; + string = strchr(string, '/'); + } while (string++); + + /* If we get here, we didn't find a match */ + return FNM_NOMATCH; } if (*string == '.' && (flags & FNM_PERIOD) && diff --git a/src/global.c b/src/global.c index 198bc1e80..e2ad8fe71 100644 --- a/src/global.c +++ b/src/global.c @@ -9,6 +9,7 @@ #include "hash.h" #include "sysdir.h" #include "filter.h" +#include "merge_driver.h" #include "openssl_stream.h" #include "thread-utils.h" #include "git2/global.h" @@ -59,6 +60,7 @@ static int init_common(void) if ((ret = git_hash_global_init()) == 0 && (ret = git_sysdir_global_init()) == 0 && (ret = git_filter_global_init()) == 0 && + (ret = git_merge_driver_global_init()) == 0 && (ret = git_transport_ssh_global_init()) == 0 && (ret = git_openssl_stream_global_init()) == 0) ret = git_mwindow_global_init(); @@ -245,6 +247,7 @@ BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved) #elif defined(GIT_THREADS) && defined(_POSIX_THREADS) static pthread_key_t _tls_key; +static pthread_mutex_t _init_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_once_t _once_init = PTHREAD_ONCE_INIT; int init_error = 0; @@ -266,12 +269,19 @@ static void init_once(void) int git_libgit2_init(void) { - int ret; + int ret, err; ret = git_atomic_inc(&git__n_inits); - pthread_once(&_once_init, init_once); - return init_error ? init_error : ret; + if ((err = pthread_mutex_lock(&_init_mutex)) != 0) + return err; + err = pthread_once(&_once_init, init_once); + err |= pthread_mutex_unlock(&_init_mutex); + + if (err || init_error) + return err | init_error; + + return ret; } int git_libgit2_shutdown(void) @@ -283,6 +293,9 @@ int git_libgit2_shutdown(void) if ((ret = git_atomic_dec(&git__n_inits)) != 0) return ret; + if ((ret = pthread_mutex_lock(&_init_mutex)) != 0) + return ret; + /* Shut down any subsystems that have global state */ shutdown_common(); @@ -296,6 +309,9 @@ int git_libgit2_shutdown(void) git_mutex_free(&git__mwindow_mutex); _once_init = new_once; + if ((ret = pthread_mutex_unlock(&_init_mutex)) != 0) + return ret; + return 0; } @@ -325,7 +341,7 @@ int git_libgit2_init(void) { int ret; - /* Only init SSL the first time */ + /* Only init subsystems the first time */ if ((ret = git_atomic_inc(&git__n_inits)) != 1) return ret; @@ -343,6 +359,7 @@ int git_libgit2_shutdown(void) if ((ret = git_atomic_dec(&git__n_inits)) == 0) { shutdown_common(); git__global_state_cleanup(&__state); + memset(&__state, 0, sizeof(__state)); } return ret; diff --git a/src/global.h b/src/global.h index 219951525..88f40aad1 100644 --- a/src/global.h +++ b/src/global.h @@ -16,6 +16,12 @@ typedef struct { git_error error_t; git_buf error_buf; char oid_fmt[GIT_OID_HEXSZ+1]; + + /* On Windows, this is the current child thread that was started by + * `git_thread_create`. This is used to set the thread's exit code + * when terminated by `git_thread_exit`. It is unused on POSIX. + */ + git_thread *current_thread; } git_global_st; #ifdef GIT_OPENSSL diff --git a/src/graph.c b/src/graph.c index 8accd808c..948f7d306 100644 --- a/src/graph.c +++ b/src/graph.c @@ -59,7 +59,7 @@ static int mark_parents(git_revwalk *walk, git_commit_list_node *one, /* as long as there are non-STALE commits */ while (interesting(&list, roots)) { git_commit_list_node *commit = git_pqueue_pop(&list); - int flags; + unsigned int flags; if (commit == NULL) break; diff --git a/src/index.c b/src/index.c index 32f585faf..42579f19a 100644 --- a/src/index.c +++ b/src/index.c @@ -19,6 +19,7 @@ #include "blob.h" #include "idxmap.h" #include "diff.h" +#include "varint.h" #include "git2/odb.h" #include "git2/oid.h" @@ -65,8 +66,11 @@ static int index_apply_to_wd_diff(git_index *index, int action, const git_strarr static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ; static const size_t INDEX_HEADER_SIZE = 12; -static const unsigned int INDEX_VERSION_NUMBER = 2; +static const unsigned int INDEX_VERSION_NUMBER_DEFAULT = 2; +static const unsigned int INDEX_VERSION_NUMBER_LB = 2; static const unsigned int INDEX_VERSION_NUMBER_EXT = 3; +static const unsigned int INDEX_VERSION_NUMBER_COMP = 4; +static const unsigned int INDEX_VERSION_NUMBER_UB = 4; static const unsigned int INDEX_HEADER_SIG = 0x44495243; static const char INDEX_EXT_TREECACHE_SIG[] = {'T', 'R', 'E', 'E'}; @@ -434,6 +438,7 @@ int git_index_open(git_index **index_out, const char *index_path) index->entries_search = git_index_entry_srch; index->entries_search_path = index_entry_srch_path; index->reuc_search = reuc_srch; + index->version = INDEX_VERSION_NUMBER_DEFAULT; if (index_path != NULL && (error = git_index_read(index, true)) < 0) goto fail; @@ -547,7 +552,7 @@ int git_index_clear(git_index *index) static int create_index_error(int error, const char *msg) { - giterr_set(GITERR_INDEX, msg); + giterr_set_str(GITERR_INDEX, msg); return error; } @@ -747,6 +752,28 @@ done: return 0; } +unsigned git_index_version(git_index *index) +{ + assert(index); + + return index->version; +} + +int git_index_set_version(git_index *index, unsigned int version) +{ + assert(index); + + if (version < INDEX_VERSION_NUMBER_LB || + version > INDEX_VERSION_NUMBER_UB) { + giterr_set(GITERR_INDEX, "Invalid version number"); + return -1; + } + + index->version = version; + + return 0; +} + int git_index_write(git_index *index) { git_indexwriter writer = GIT_INDEXWRITER_INIT; @@ -2160,12 +2187,12 @@ static int read_reuc(git_index *index, const char *buffer, size_t size) if (git__strtol64(&tmp, buffer, &endptr, 8) < 0 || !endptr || endptr == buffer || *endptr || - tmp < 0) { + tmp < 0 || tmp > UINT32_MAX) { index_entry_reuc_free(lost); return index_error_invalid("reading reuc entry stage"); } - lost->mode[i] = tmp; + lost->mode[i] = (uint32_t)tmp; len = (endptr + 1) - buffer; if (size <= len) { @@ -2262,12 +2289,15 @@ static size_t read_entry( git_index_entry **out, git_index *index, const void *buffer, - size_t buffer_size) + size_t buffer_size, + const char **last) { size_t path_length, entry_size; const char *path_ptr; struct entry_short source; git_index_entry entry = {{0}}; + bool compressed = index->version >= INDEX_VERSION_NUMBER_COMP; + char *tmp_path = NULL; if (INDEX_FOOTER_SIZE + minimal_entry_size > buffer_size) return 0; @@ -2302,33 +2332,56 @@ static size_t read_entry( } else path_ptr = (const char *) buffer + offsetof(struct entry_short, path); - path_length = entry.flags & GIT_IDXENTRY_NAMEMASK; + if (!compressed) { + path_length = entry.flags & GIT_IDXENTRY_NAMEMASK; - /* if this is a very long string, we must find its - * real length without overflowing */ - if (path_length == 0xFFF) { - const char *path_end; + /* if this is a very long string, we must find its + * real length without overflowing */ + if (path_length == 0xFFF) { + const char *path_end; - path_end = memchr(path_ptr, '\0', buffer_size); - if (path_end == NULL) + path_end = memchr(path_ptr, '\0', buffer_size); + if (path_end == NULL) + return 0; + + path_length = path_end - path_ptr; + } + + if (entry.flags & GIT_IDXENTRY_EXTENDED) + entry_size = long_entry_size(path_length); + else + entry_size = short_entry_size(path_length); + + if (INDEX_FOOTER_SIZE + entry_size > buffer_size) return 0; - path_length = path_end - path_ptr; + entry.path = (char *)path_ptr; + } else { + size_t varint_len; + size_t shared = git_decode_varint((const unsigned char *)path_ptr, + &varint_len); + size_t len = strlen(path_ptr + varint_len); + size_t last_len = strlen(*last); + size_t tmp_path_len; + + if (varint_len == 0) + return index_error_invalid("incorrect prefix length"); + + GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1); + tmp_path = git__malloc(tmp_path_len); + GITERR_CHECK_ALLOC(tmp_path); + memcpy(tmp_path, last, last_len); + memcpy(tmp_path + last_len, path_ptr + varint_len, len); + entry_size = long_entry_size(shared + len); + entry.path = tmp_path; } - if (entry.flags & GIT_IDXENTRY_EXTENDED) - entry_size = long_entry_size(path_length); - else - entry_size = short_entry_size(path_length); - - if (INDEX_FOOTER_SIZE + entry_size > buffer_size) - return 0; - - entry.path = (char *)path_ptr; - - if (index_entry_dup(out, index, &entry) < 0) + if (index_entry_dup(out, index, &entry) < 0) { + git__free(tmp_path); return 0; + } + git__free(tmp_path); return entry_size; } @@ -2341,8 +2394,8 @@ static int read_header(struct index_header *dest, const void *buffer) return index_error_invalid("incorrect header signature"); dest->version = ntohl(source->version); - if (dest->version != INDEX_VERSION_NUMBER_EXT && - dest->version != INDEX_VERSION_NUMBER) + if (dest->version < INDEX_VERSION_NUMBER_LB || + dest->version > INDEX_VERSION_NUMBER_UB) return index_error_invalid("incorrect header version"); dest->entry_count = ntohl(source->entry_count); @@ -2395,6 +2448,8 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) unsigned int i; struct index_header header = { 0 }; git_oid checksum_calculated, checksum_expected; + const char **last = NULL; + const char *empty = ""; #define seek_forward(_increase) { \ if (_increase >= buffer_size) { \ @@ -2415,6 +2470,10 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) if ((error = read_header(&header, buffer)) < 0) return error; + index->version = header.version; + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = ∅ + seek_forward(INDEX_HEADER_SIZE); assert(!index->entries.length); @@ -2427,7 +2486,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { git_index_entry *entry; - size_t entry_size = read_entry(&entry, index, buffer, buffer_size); + size_t entry_size = read_entry(&entry, index, buffer, buffer_size, last); /* 0 bytes read means an object corruption */ if (entry_size == 0) { @@ -2518,15 +2577,31 @@ static bool is_index_extended(git_index *index) return (extended > 0); } -static int write_disk_entry(git_filebuf *file, git_index_entry *entry) +static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last) { void *mem = NULL; struct entry_short *ondisk; size_t path_len, disk_size; char *path; + const char *path_start = entry->path; + size_t same_len = 0; path_len = ((struct entry_internal *)entry)->pathlen; + if (last) { + const char *last_c = *last; + + while (*path_start == *last_c) { + if (!*path_start || !*last_c) + break; + ++path_start; + ++last_c; + ++same_len; + } + path_len -= same_len; + *last = entry->path; + } + if (entry->flags & GIT_IDXENTRY_EXTENDED) disk_size = long_entry_size(path_len); else @@ -2574,7 +2649,12 @@ static int write_disk_entry(git_filebuf *file, git_index_entry *entry) else path = ondisk->path; - memcpy(path, entry->path, path_len); + if (last) { + path += git_encode_varint((unsigned char *) path, + disk_size, + path_len - same_len); + } + memcpy(path, path_start, path_len); return 0; } @@ -2585,6 +2665,8 @@ static int write_entries(git_index *index, git_filebuf *file) size_t i; git_vector case_sorted, *entries; git_index_entry *entry; + const char **last = NULL; + const char *empty = ""; /* If index->entries is sorted case-insensitively, then we need * to re-sort it case-sensitively before writing */ @@ -2596,8 +2678,11 @@ static int write_entries(git_index *index, git_filebuf *file) entries = &index->entries; } + if (index->version >= INDEX_VERSION_NUMBER_COMP) + last = ∅ + git_vector_foreach(entries, i, entry) - if ((error = write_disk_entry(file, entry)) < 0) + if ((error = write_disk_entry(file, entry, last)) < 0) break; if (index->ignore_case) @@ -2762,8 +2847,12 @@ static int write_index(git_oid *checksum, git_index *index, git_filebuf *file) assert(index && file); - is_extended = is_index_extended(index); - index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER; + if (index->version <= INDEX_VERSION_NUMBER_EXT) { + is_extended = is_index_extended(index); + index_version_number = is_extended ? INDEX_VERSION_NUMBER_EXT : INDEX_VERSION_NUMBER_LB; + } else { + index_version_number = index->version; + } header.signature = htonl(INDEX_HEADER_SIG); header.version = htonl(index_version_number); @@ -2925,38 +3014,39 @@ cleanup: return error; } -int git_index_read_index( +static int git_index_read_iterator( git_index *index, - const git_index *new_index) + git_iterator *new_iterator, + size_t new_length_hint) { git_vector new_entries = GIT_VECTOR_INIT, remove_entries = GIT_VECTOR_INIT; git_idxmap *new_entries_map = NULL; git_iterator *index_iterator = NULL; - git_iterator *new_iterator = NULL; git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *old_entry, *new_entry; git_index_entry *entry; size_t i; int error; - if ((error = git_vector_init(&new_entries, new_index->entries.length, index->entries._cmp)) < 0 || + assert((new_iterator->flags & GIT_ITERATOR_DONT_IGNORE_CASE)); + + if ((error = git_vector_init(&new_entries, new_length_hint, index->entries._cmp)) < 0 || (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0 || (error = git_idxmap_alloc(&new_entries_map)) < 0) goto done; - if (index->ignore_case) - kh_resize(idxicase, (khash_t(idxicase) *) new_entries_map, new_index->entries.length); - else - kh_resize(idx, new_entries_map, new_index->entries.length); + if (index->ignore_case && new_length_hint) + kh_resize(idxicase, (khash_t(idxicase) *) new_entries_map, new_length_hint); + else if (new_length_hint) + kh_resize(idx, new_entries_map, new_length_hint); - opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; - if ((error = git_iterator_for_index(&index_iterator, git_index_owner(index), index, &opts)) < 0 || - (error = git_iterator_for_index(&new_iterator, git_index_owner(new_index), (git_index *)new_index, &opts)) < 0) - goto done; - - if (((error = git_iterator_current(&old_entry, index_iterator)) < 0 && + if ((error = git_iterator_for_index(&index_iterator, + git_index_owner(index), index, &opts)) < 0 || + ((error = git_iterator_current(&old_entry, index_iterator)) < 0 && error != GIT_ITEROVER) || ((error = git_iterator_current(&new_entry, new_iterator)) < 0 && error != GIT_ITEROVER)) @@ -3050,6 +3140,8 @@ int git_index_read_index( index_entry_free(entry); } + clear_uptodate(index); + error = 0; done: @@ -3057,6 +3149,27 @@ done: git_vector_free(&new_entries); git_vector_free(&remove_entries); git_iterator_free(index_iterator); + return error; +} + +int git_index_read_index( + git_index *index, + const git_index *new_index) +{ + git_iterator *new_iterator = NULL; + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + int error; + + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_INCLUDE_CONFLICTS; + + if ((error = git_iterator_for_index(&new_iterator, + git_index_owner(new_index), (git_index *)new_index, &opts)) < 0 || + (error = git_index_read_iterator(index, new_iterator, + new_index->entries.length)) < 0) + goto done; + +done: git_iterator_free(new_iterator); return error; } diff --git a/src/index.h b/src/index.h index 8b9b49498..9918f140d 100644 --- a/src/index.h +++ b/src/index.h @@ -46,6 +46,8 @@ struct git_index { git_vector_cmp entries_search; git_vector_cmp entries_search_path; git_vector_cmp reuc_search; + + unsigned int version; }; struct git_index_conflict_iterator { diff --git a/src/iterator.c b/src/iterator.c index cb1ea6a87..8fc62c01c 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -8,106 +8,320 @@ #include "iterator.h" #include "tree.h" #include "index.h" -#include "ignore.h" -#include "buffer.h" -#include "submodule.h" -#include -#define ITERATOR_SET_CB(P,NAME_LC) do { \ - (P)->cb.current = NAME_LC ## _iterator__current; \ - (P)->cb.advance = NAME_LC ## _iterator__advance; \ - (P)->cb.advance_into = NAME_LC ## _iterator__advance_into; \ - (P)->cb.seek = NAME_LC ## _iterator__seek; \ - (P)->cb.reset = NAME_LC ## _iterator__reset; \ - (P)->cb.at_end = NAME_LC ## _iterator__at_end; \ - (P)->cb.free = NAME_LC ## _iterator__free; \ - } while (0) - -#define ITERATOR_CASE_FLAGS \ - (GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_IGNORE_CASE) - -#define ITERATOR_BASE_INIT(P,NAME_LC,NAME_UC,REPO) do { \ - (P)->base.type = GIT_ITERATOR_TYPE_ ## NAME_UC; \ - (P)->base.cb = &(P)->cb; \ - ITERATOR_SET_CB(P,NAME_LC); \ - (P)->base.repo = (REPO); \ - (P)->base.start = options && options->start ? \ - git__strdup(options->start) : NULL; \ - (P)->base.end = options && options->end ? \ - git__strdup(options->end) : NULL; \ - if ((options && options->start && !(P)->base.start) || \ - (options && options->end && !(P)->base.end)) { \ - git__free(P); return -1; } \ - (P)->base.strcomp = git__strcmp; \ - (P)->base.strncomp = git__strncmp; \ - (P)->base.prefixcomp = git__prefixcmp; \ - (P)->base.flags = options ? options->flags & ~ITERATOR_CASE_FLAGS : 0; \ - if ((P)->base.flags & GIT_ITERATOR_DONT_AUTOEXPAND) \ - (P)->base.flags |= GIT_ITERATOR_INCLUDE_TREES; \ - if (options && options->pathlist.count && \ - iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \ - git__free(P); return -1; } \ - } while (0) +#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) +#define GIT_ITERATOR_HONOR_IGNORES (1 << 16) +#define GIT_ITERATOR_IGNORE_DOT_GIT (1 << 17) #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) -#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) -#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) -#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) -#define iterator__include_conflicts(I) iterator__flag(I, INCLUDE_CONFLICTS) - -#define GIT_ITERATOR_FIRST_ACCESS (1 << 15) +#define iterator__ignore_case(I) iterator__flag(I,IGNORE_CASE) +#define iterator__include_trees(I) iterator__flag(I,INCLUDE_TREES) +#define iterator__dont_autoexpand(I) iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__do_autoexpand(I) !iterator__flag(I,DONT_AUTOEXPAND) +#define iterator__include_conflicts(I) iterator__flag(I,INCLUDE_CONFLICTS) #define iterator__has_been_accessed(I) iterator__flag(I,FIRST_ACCESS) - -#define iterator__end(I) ((git_iterator *)(I))->end -#define iterator__past_end(I,PATH) \ - (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0) +#define iterator__honor_ignores(I) iterator__flag(I,HONOR_IGNORES) +#define iterator__ignore_dot_git(I) iterator__flag(I,IGNORE_DOT_GIT) -typedef enum { - ITERATOR_PATHLIST_NONE = 0, - ITERATOR_PATHLIST_MATCH = 1, - ITERATOR_PATHLIST_MATCH_DIRECTORY = 2, - ITERATOR_PATHLIST_MATCH_CHILD = 3, -} iterator_pathlist__match_t; - -static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec) +static void iterator_set_ignore_case(git_iterator *iter, bool ignore_case) { - size_t i; + if (ignore_case) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; - if (git_vector_init(&iter->pathlist, pathspec->count, - (git_vector_cmp)iter->strcomp) < 0) - return -1; + iter->strcomp = ignore_case ? git__strcasecmp : git__strcmp; + iter->strncomp = ignore_case ? git__strncasecmp : git__strncmp; + iter->prefixcomp = ignore_case ? git__prefixcmp_icase : git__prefixcmp; + iter->entry_srch = ignore_case ? git_index_entry_isrch : git_index_entry_srch; - for (i = 0; i < pathspec->count; i++) { - if (!pathspec->strings[i]) - continue; + git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); +} - if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0) - return -1; +static int iterator_range_init( + git_iterator *iter, const char *start, const char *end) +{ + if (start && *start) { + iter->start = git__strdup(start); + GITERR_CHECK_ALLOC(iter->start); + + iter->start_len = strlen(iter->start); } - git_vector_sort(&iter->pathlist); + if (end && *end) { + iter->end = git__strdup(end); + GITERR_CHECK_ALLOC(iter->end); + + iter->end_len = strlen(iter->end); + } + + iter->started = (iter->start == NULL); + iter->ended = false; return 0; } -static iterator_pathlist__match_t iterator_pathlist__match( +static void iterator_range_free(git_iterator *iter) +{ + if (iter->start) { + git__free(iter->start); + iter->start = NULL; + iter->start_len = 0; + } + + if (iter->end) { + git__free(iter->end); + iter->end = NULL; + iter->end_len = 0; + } +} + +static int iterator_reset_range( + git_iterator *iter, const char *start, const char *end) +{ + iterator_range_free(iter); + return iterator_range_init(iter, start, end); +} + +static int iterator_pathlist_init(git_iterator *iter, git_strarray *pathlist) +{ + size_t i; + + if (git_vector_init(&iter->pathlist, pathlist->count, NULL) < 0) + return -1; + + for (i = 0; i < pathlist->count; i++) { + if (!pathlist->strings[i]) + continue; + + if (git_vector_insert(&iter->pathlist, pathlist->strings[i]) < 0) + return -1; + } + + return 0; +} + +static int iterator_init_common( + git_iterator *iter, + git_repository *repo, + git_index *index, + git_iterator_options *given_opts) +{ + static git_iterator_options default_opts = GIT_ITERATOR_OPTIONS_INIT; + git_iterator_options *options = given_opts ? given_opts : &default_opts; + bool ignore_case; + int precompose; + int error; + + iter->repo = repo; + iter->index = index; + iter->flags = options->flags; + + if ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0) { + ignore_case = true; + } else if ((iter->flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) { + ignore_case = false; + } else if (repo) { + git_index *index; + + if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) + return error; + + ignore_case = !!index->ignore_case; + + if (ignore_case == 1) + iter->flags |= GIT_ITERATOR_IGNORE_CASE; + else + iter->flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + } else { + ignore_case = false; + } + + /* try to look up precompose and set flag if appropriate */ + if (repo && + (iter->flags & GIT_ITERATOR_PRECOMPOSE_UNICODE) == 0 && + (iter->flags & GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE) == 0) { + + if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0) + giterr_clear(); + else if (precompose) + iter->flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; + } + + if ((iter->flags & GIT_ITERATOR_DONT_AUTOEXPAND)) + iter->flags |= GIT_ITERATOR_INCLUDE_TREES; + + if ((error = iterator_range_init(iter, options->start, options->end)) < 0 || + (error = iterator_pathlist_init(iter, &options->pathlist)) < 0) + return error; + + iterator_set_ignore_case(iter, ignore_case); + return 0; +} + +static void iterator_clear(git_iterator *iter) +{ + iter->started = false; + iter->ended = false; + iter->stat_calls = 0; + iter->pathlist_walk_idx = 0; + iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; +} + +GIT_INLINE(bool) iterator_has_started( + git_iterator *iter, const char *path, bool is_submodule) +{ + size_t path_len; + + if (iter->start == NULL || iter->started == true) + return true; + + /* the starting path is generally a prefix - we have started once we + * are prefixed by this path + */ + iter->started = (iter->prefixcomp(path, iter->start) >= 0); + + if (iter->started) + return true; + + path_len = strlen(path); + + /* if, however, we are a submodule, then we support `start` being + * suffixed with a `/` for crazy legacy reasons. match `submod` + * with a start path of `submod/`. + */ + if (is_submodule && iter->start_len && path_len == iter->start_len - 1 && + iter->start[iter->start_len-1] == '/') + return true; + + /* if, however, our current path is a directory, and our starting path + * is _beneath_ that directory, then recurse into the directory (even + * though we have not yet "started") + */ + if (path_len > 0 && path[path_len-1] == '/' && + iter->strncomp(path, iter->start, path_len) == 0) + return true; + + return false; +} + +GIT_INLINE(bool) iterator_has_ended(git_iterator *iter, const char *path) +{ + if (iter->end == NULL) + return false; + else if (iter->ended) + return true; + + iter->ended = (iter->prefixcomp(path, iter->end) > 0); + return iter->ended; +} + +/* walker for the index and tree iterator that allows it to walk the sorted + * pathlist entries alongside sorted iterator entries. + */ +static bool iterator_pathlist_next_is(git_iterator *iter, const char *path) +{ + char *p; + size_t path_len, p_len, cmp_len, i; + int cmp; + + if (iter->pathlist.length == 0) + return true; + + git_vector_sort(&iter->pathlist); + + path_len = strlen(path); + + /* for comparison, drop the trailing slash on the current '/' */ + if (path_len && path[path_len-1] == '/') + path_len--; + + for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { + p = iter->pathlist.contents[i]; + p_len = strlen(p); + + if (p_len && p[p_len-1] == '/') + p_len--; + + cmp_len = min(path_len, p_len); + + /* see if the pathlist entry is a prefix of this path */ + cmp = iter->strncomp(p, path, cmp_len); + + /* prefix match - see if there's an exact match, or if we were + * given a path that matches the directory + */ + if (cmp == 0) { + /* if this pathlist entry is not suffixed with a '/' then + * it matches a path that is a file or a directory. + * (eg, pathlist = "foo" and path is "foo" or "foo/" or + * "foo/something") + */ + if (p[cmp_len] == '\0' && + (path[cmp_len] == '\0' || path[cmp_len] == '/')) + return true; + + /* if this pathlist entry _is_ suffixed with a '/' then + * it matches only paths that are directories. + * (eg, pathlist = "foo/" and path is "foo/" or "foo/something") + */ + if (p[cmp_len] == '/' && path[cmp_len] == '/') + return true; + } + + /* this pathlist entry sorts before the given path, try the next */ + else if (cmp < 0) { + iter->pathlist_walk_idx++; + continue; + } + + /* this pathlist sorts after the given path, no match. */ + else if (cmp > 0) { + break; + } + } + + return false; +} + +typedef enum { + ITERATOR_PATHLIST_NONE = 0, + ITERATOR_PATHLIST_IS_FILE = 1, + ITERATOR_PATHLIST_IS_DIR = 2, + ITERATOR_PATHLIST_IS_PARENT = 3, + ITERATOR_PATHLIST_FULL = 4, +} iterator_pathlist_search_t; + +static iterator_pathlist_search_t iterator_pathlist_search( git_iterator *iter, const char *path, size_t path_len) { const char *p; size_t idx; int error; + if (iter->pathlist.length == 0) + return ITERATOR_PATHLIST_FULL; + + git_vector_sort(&iter->pathlist); + error = git_vector_bsearch2(&idx, &iter->pathlist, (git_vector_cmp)iter->strcomp, path); - if (error == 0) - return ITERATOR_PATHLIST_MATCH; + /* the given path was found in the pathlist. since the pathlist only + * matches directories when they're suffixed with a '/', analyze the + * path string to determine whether it's a directory or not. + */ + if (error == 0) { + if (path_len && path[path_len-1] == '/') + return ITERATOR_PATHLIST_IS_DIR; + + return ITERATOR_PATHLIST_IS_FILE; + } /* at this point, the path we're examining may be a directory (though we * don't know that yet, since we're avoiding a stat unless it's necessary) - * so see if the pathlist contains a file beneath this directory. + * so walk the pathlist looking for the given path with a '/' after it, */ while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { if (iter->prefixcomp(p, path) != 0) @@ -119,8 +333,8 @@ static iterator_pathlist__match_t iterator_pathlist__match( /* is this a literal directory entry (eg `foo/`) or a file beneath */ if (p[path_len] == '/') { return (p[path_len+1] == '\0') ? - ITERATOR_PATHLIST_MATCH_DIRECTORY : - ITERATOR_PATHLIST_MATCH_CHILD; + ITERATOR_PATHLIST_IS_DIR : + ITERATOR_PATHLIST_IS_PARENT; } if (p[path_len] > '/') @@ -132,164 +346,34 @@ static iterator_pathlist__match_t iterator_pathlist__match( return ITERATOR_PATHLIST_NONE; } -static void iterator_pathlist_walk__reset(git_iterator *iter) -{ - iter->pathlist_walk_idx = 0; -} +/* Empty iterator */ -/* walker for the index iterator that allows it to walk the sorted pathlist - * entries alongside the sorted index entries. the `iter->pathlist_walk_idx` - * stores the starting position for subsequent calls, the position is advanced - * along with the index iterator, with a special case for handling directories - * in the pathlist that are specified without trailing '/'. (eg, `foo`). - * we do not advance over these entries until we're certain that the index - * iterator will not ask us for a file beneath that directory (eg, `foo/bar`). - */ -static bool iterator_pathlist_walk__contains(git_iterator *iter, const char *path) -{ - size_t i; - char *p; - size_t p_len; - int cmp; - - for (i = iter->pathlist_walk_idx; i < iter->pathlist.length; i++) { - p = iter->pathlist.contents[i]; - p_len = strlen(p); - - /* see if the pathlist entry is a prefix of this path */ - cmp = iter->strncomp(p, path, p_len); - - /* this pathlist entry sorts before the given path, try the next */ - if (!p_len || cmp < 0) - iter->pathlist_walk_idx++; - - /* this pathlist sorts after the given path, no match. */ - else if (cmp > 0) - return false; - - /* match! an exact match (`foo` vs `foo`), the path is a child of an - * explicit directory in the pathlist (`foo/` vs `foo/bar`) or the path - * is a child of an entry in the pathlist (`foo` vs `foo/bar`) - */ - else if (path[p_len] == '\0' || p[p_len - 1] == '/' || path[p_len] == '/') - return true; - - /* only advance the start index for future callers if we know that we - * will not see a child of this path. eg, a pathlist entry `foo` is - * a prefix for `foo.txt` and `foo/bar`. don't advance the start - * pathlist index when we see `foo.txt` or we would miss a subsequent - * inspection of `foo/bar`. only advance when there are no more - * potential children. - */ - else if (path[p_len] > '/') - iter->pathlist_walk_idx++; - } - - return false; -} - -static void iterator_pathlist__update_ignore_case(git_iterator *iter) -{ - git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); - git_vector_sort(&iter->pathlist); - - iter->pathlist_walk_idx = 0; -} - - -static int iterator__reset_range( - git_iterator *iter, const char *start, const char *end) -{ - if (start) { - if (iter->start) - git__free(iter->start); - iter->start = git__strdup(start); - GITERR_CHECK_ALLOC(iter->start); - } - - if (end) { - if (iter->end) - git__free(iter->end); - iter->end = git__strdup(end); - GITERR_CHECK_ALLOC(iter->end); - } - - iter->flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int iterator__update_ignore_case( - git_iterator *iter, - git_iterator_flag_t flags) -{ - bool ignore_case; - int error; - - if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) - ignore_case = true; - else if ((flags & GIT_ITERATOR_DONT_IGNORE_CASE) != 0) - ignore_case = false; - else { - git_index *index; - - if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) - return error; - - ignore_case = (index->ignore_case == 1); - } - - if (ignore_case) { - iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE); - - iter->strcomp = git__strcasecmp; - iter->strncomp = git__strncasecmp; - iter->prefixcomp = git__prefixcmp_icase; - } else { - iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); - - iter->strcomp = git__strcmp; - iter->strncomp = git__strncmp; - iter->prefixcomp = git__prefixcmp; - } - - iterator_pathlist__update_ignore_case(iter); - - return 0; -} - -GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry) -{ - if (entry) *entry = NULL; -} - - -static int empty_iterator__noop(const git_index_entry **e, git_iterator *i) +static int empty_iterator_noop(const git_index_entry **e, git_iterator *i) { GIT_UNUSED(i); - iterator__clear_entry(e); + + if (e) + *e = NULL; + return GIT_ITEROVER; } -static int empty_iterator__seek(git_iterator *i, const char *p) +static int empty_iterator_advance_over( + const git_index_entry **e, + git_iterator_status_t *s, + git_iterator *i) { - GIT_UNUSED(i); GIT_UNUSED(p); - return -1; + *s = GIT_ITERATOR_STATUS_EMPTY; + return empty_iterator_noop(e, i); } -static int empty_iterator__reset(git_iterator *i, const char *s, const char *e) +static int empty_iterator_reset(git_iterator *i) { - GIT_UNUSED(i); GIT_UNUSED(s); GIT_UNUSED(e); + GIT_UNUSED(i); return 0; } -static int empty_iterator__at_end(git_iterator *i) -{ - GIT_UNUSED(i); - return 1; -} - -static void empty_iterator__free(git_iterator *i) +static void empty_iterator_free(git_iterator *i) { GIT_UNUSED(i); } @@ -300,1327 +384,1500 @@ typedef struct { } empty_iterator; int git_iterator_for_nothing( - git_iterator **iter, + git_iterator **out, git_iterator_options *options) { - empty_iterator *i = git__calloc(1, sizeof(empty_iterator)); - GITERR_CHECK_ALLOC(i); + empty_iterator *iter; -#define empty_iterator__current empty_iterator__noop -#define empty_iterator__advance empty_iterator__noop -#define empty_iterator__advance_into empty_iterator__noop + static git_iterator_callbacks callbacks = { + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_noop, + empty_iterator_advance_over, + empty_iterator_reset, + empty_iterator_free + }; - ITERATOR_BASE_INIT(i, empty, EMPTY, NULL); + *out = NULL; - if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) - i->base.flags |= GIT_ITERATOR_IGNORE_CASE; + iter = git__calloc(1, sizeof(empty_iterator)); + GITERR_CHECK_ALLOC(iter); - *iter = (git_iterator *)i; + iter->base.type = GIT_ITERATOR_TYPE_EMPTY; + iter->base.cb = &callbacks; + iter->base.flags = options->flags; + + *out = &iter->base; return 0; } +/* Tree iterator */ -typedef struct tree_iterator_entry tree_iterator_entry; -struct tree_iterator_entry { - tree_iterator_entry *parent; - const git_tree_entry *te; +typedef struct { + git_tree_entry *tree_entry; + const char *parent_path; +} tree_iterator_entry; + +typedef struct { git_tree *tree; -}; -typedef struct tree_iterator_frame tree_iterator_frame; -struct tree_iterator_frame { - tree_iterator_frame *up, *down; + /* path to this particular frame (folder) */ + git_buf path; - size_t n_entries; /* items in this frame */ - size_t current; /* start of currently active range in frame */ - size_t next; /* start of next range in frame */ + /* a sorted list of the entries for this frame (folder), these are + * actually pointers to the iterator's entry pool. + */ + git_vector entries; + tree_iterator_entry *current; - const char *start; - size_t startlen; + size_t next_idx; - tree_iterator_entry *entries[GIT_FLEX_ARRAY]; -}; + /* on case insensitive iterations, we also have an array of other + * paths that were case insensitively equal to this one, and their + * tree objects. we have coalesced the tree entries into this frame. + * a child `tree_iterator_entry` will contain a pointer to its actual + * parent path. + */ + git_vector similar_trees; + git_array_t(git_buf) similar_paths; +} tree_iterator_frame; typedef struct { git_iterator base; - git_iterator_callbacks cb; - tree_iterator_frame *head, *root; - git_pool pool; + git_tree *root; + git_array_t(tree_iterator_frame) frames; + git_index_entry entry; - git_buf path; - int path_ambiguities; - bool path_has_filename; - bool entry_is_current; + git_buf entry_path; + + /* a pool of entries to reduce the number of allocations */ + git_pool entry_pool; } tree_iterator; -static char *tree_iterator__current_filename( - tree_iterator *ti, const git_tree_entry *te) +GIT_INLINE(tree_iterator_frame *) tree_iterator_parent_frame( + tree_iterator *iter) { - if (!ti->path_has_filename) { - if (git_buf_joinpath(&ti->path, ti->path.ptr, te->filename) < 0) - return NULL; - - if (git_tree_entry__is_tree(te) && git_buf_putc(&ti->path, '/') < 0) - return NULL; - - ti->path_has_filename = true; - } - - return ti->path.ptr; + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; } -static void tree_iterator__rewrite_filename(tree_iterator *ti) +GIT_INLINE(tree_iterator_frame *) tree_iterator_current_frame( + tree_iterator *iter) { - tree_iterator_entry *scan = ti->head->entries[ti->head->current]; - ssize_t strpos = ti->path.size; - const git_tree_entry *te; - - if (strpos && ti->path.ptr[strpos - 1] == '/') - strpos--; - - for (; scan && (te = scan->te); scan = scan->parent) { - strpos -= te->filename_len; - memcpy(&ti->path.ptr[strpos], te->filename, te->filename_len); - strpos -= 1; /* separator */ - } + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; } -static int tree_iterator__te_cmp( - const git_tree_entry *a, - const git_tree_entry *b, - int (*compare)(const char *, const char *, size_t)) +GIT_INLINE(int) tree_entry_cmp( + const git_tree_entry *a, const git_tree_entry *b, bool icase) { return git_path_cmp( a->filename, a->filename_len, a->attr == GIT_FILEMODE_TREE, b->filename, b->filename_len, b->attr == GIT_FILEMODE_TREE, - compare); + icase ? git__strncasecmp : git__strncmp); } -static int tree_iterator__ci_cmp(const void *a, const void *b, void *p) +GIT_INLINE(int) tree_iterator_entry_cmp(const void *ptr_a, const void *ptr_b) { - const tree_iterator_entry *ae = a, *be = b; - int cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncasecmp); + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - if (!cmp) { - /* stabilize sort order among equivalent names */ - if (!ae->parent->te || !be->parent->te) - cmp = tree_iterator__te_cmp(ae->te, be->te, git__strncmp); - else - cmp = tree_iterator__ci_cmp(ae->parent, be->parent, p); - } - - return cmp; + return tree_entry_cmp(a->tree_entry, b->tree_entry, false); } -static int tree_iterator__search_cmp(const void *key, const void *val, void *p) +GIT_INLINE(int) tree_iterator_entry_cmp_icase( + const void *ptr_a, const void *ptr_b) { - const tree_iterator_frame *tf = key; - const git_tree_entry *te = ((tree_iterator_entry *)val)->te; + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - return git_path_cmp( - tf->start, tf->startlen, false, - te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE, - ((git_iterator *)p)->strncomp); + return tree_entry_cmp(a->tree_entry, b->tree_entry, true); } -static bool tree_iterator__move_to_next( - tree_iterator *ti, tree_iterator_frame *tf) +static int tree_iterator_entry_sort_icase(const void *ptr_a, const void *ptr_b) { - if (tf->next > tf->current + 1) - ti->path_ambiguities--; + const tree_iterator_entry *a = (const tree_iterator_entry *)ptr_a; + const tree_iterator_entry *b = (const tree_iterator_entry *)ptr_b; - if (!tf->up) { /* at root */ - tf->current = tf->next; - return false; - } + int c = tree_entry_cmp(a->tree_entry, b->tree_entry, true); - for (; tf->current < tf->next; tf->current++) { - git_tree_free(tf->entries[tf->current]->tree); - tf->entries[tf->current]->tree = NULL; - } + /* stabilize the sort order for filenames that are (case insensitively) + * the same by examining the parent path (case sensitively) before + * falling back to a case sensitive sort of the filename. + */ + if (!c && a->parent_path != b->parent_path) + c = git__strcmp(a->parent_path, b->parent_path); - return (tf->current < tf->n_entries); + if (!c) + c = tree_entry_cmp(a->tree_entry, b->tree_entry, false); + + return c; } -static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf) +static int tree_iterator_compute_path( + git_buf *out, + tree_iterator_entry *entry) { - int error = 0; - const git_tree_entry *te, *last = NULL; + git_buf_clear(out); - tf->next = tf->current; + if (entry->parent_path) + git_buf_joinpath(out, entry->parent_path, entry->tree_entry->filename); + else + git_buf_puts(out, entry->tree_entry->filename); - for (; tf->next < tf->n_entries; tf->next++, last = te) { - te = tf->entries[tf->next]->te; + if (git_tree_entry__is_tree(entry->tree_entry)) + git_buf_putc(out, '/'); - if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp)) - break; - - /* try to load trees for items in [current,next) range */ - if (!error && git_tree_entry__is_tree(te)) - error = git_tree_lookup( - &tf->entries[tf->next]->tree, ti->base.repo, te->oid); - } - - if (tf->next > tf->current + 1) - ti->path_ambiguities++; - - /* if a tree lookup failed, advance over this span and return failure */ - if (error < 0) { - tree_iterator__move_to_next(ti, tf); - return error; - } - - if (last && !tree_iterator__current_filename(ti, last)) - return -1; /* must have been allocation failure */ - - return 0; -} - -GIT_INLINE(bool) tree_iterator__at_tree(tree_iterator *ti) -{ - return (ti->head->current < ti->head->n_entries && - ti->head->entries[ti->head->current]->tree != NULL); -} - -static int tree_iterator__push_frame(tree_iterator *ti) -{ - int error = 0; - tree_iterator_frame *head = ti->head, *tf = NULL; - size_t i, n_entries = 0, alloclen; - - if (head->current >= head->n_entries || !head->entries[head->current]->tree) - return GIT_ITEROVER; - - for (i = head->current; i < head->next; ++i) - n_entries += git_tree_entrycount(head->entries[i]->tree); - - GITERR_CHECK_ALLOC_MULTIPLY(&alloclen, sizeof(tree_iterator_entry *), n_entries); - GITERR_CHECK_ALLOC_ADD(&alloclen, alloclen, sizeof(tree_iterator_frame)); - - tf = git__calloc(1, alloclen); - GITERR_CHECK_ALLOC(tf); - - tf->n_entries = n_entries; - - tf->up = head; - head->down = tf; - ti->head = tf; - - for (i = head->current, n_entries = 0; i < head->next; ++i) { - git_tree *tree = head->entries[i]->tree; - size_t j, max_j = git_tree_entrycount(tree); - - for (j = 0; j < max_j; ++j) { - tree_iterator_entry *entry = git_pool_malloc(&ti->pool, 1); - GITERR_CHECK_ALLOC(entry); - - entry->parent = head->entries[i]; - entry->te = git_tree_entry_byindex(tree, j); - entry->tree = NULL; - - tf->entries[n_entries++] = entry; - } - } - - /* if ignore_case, sort entries case insensitively */ - if (iterator__ignore_case(ti)) - git__tsort_r( - (void **)tf->entries, tf->n_entries, tree_iterator__ci_cmp, tf); - - /* pick tf->current based on "start" (or start at zero) */ - if (head->startlen > 0) { - git__bsearch_r((void **)tf->entries, tf->n_entries, head, - tree_iterator__search_cmp, ti, &tf->current); - - while (tf->current && - !tree_iterator__search_cmp(head, tf->entries[tf->current-1], ti)) - tf->current--; - - if ((tf->start = strchr(head->start, '/')) != NULL) { - tf->start++; - tf->startlen = strlen(tf->start); - } - } - - ti->path_has_filename = ti->entry_is_current = false; - - if ((error = tree_iterator__set_next(ti, tf)) < 0) - return error; - - /* autoexpand as needed */ - if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti)) - return tree_iterator__push_frame(ti); - - return 0; -} - -static bool tree_iterator__pop_frame(tree_iterator *ti, bool final) -{ - tree_iterator_frame *tf = ti->head; - - assert(tf); - - if (!tf->up) - return false; - - ti->head = tf->up; - ti->head->down = NULL; - - tree_iterator__move_to_next(ti, tf); - - if (!final) { /* if final, don't bother to clean up */ - // TODO: maybe free the pool so far? - git_buf_rtruncate_at_char(&ti->path, '/'); - } - - git__free(tf); - - return true; -} - -static void tree_iterator__pop_all(tree_iterator *ti, bool to_end, bool final) -{ - while (tree_iterator__pop_frame(ti, final)) /* pop to root */; - - if (!final) { - assert(ti->head); - - ti->head->current = to_end ? ti->head->n_entries : 0; - ti->path_ambiguities = 0; - git_buf_clear(&ti->path); - } -} - -static int tree_iterator__update_entry(tree_iterator *ti) -{ - tree_iterator_frame *tf; - const git_tree_entry *te; - - if (ti->entry_is_current) - return 0; - - tf = ti->head; - te = tf->entries[tf->current]->te; - - ti->entry.mode = te->attr; - git_oid_cpy(&ti->entry.id, te->oid); - - ti->entry.path = tree_iterator__current_filename(ti, te); - GITERR_CHECK_ALLOC(ti->entry.path); - - if (ti->path_ambiguities > 0) - tree_iterator__rewrite_filename(ti); - - if (iterator__past_end(ti, ti->entry.path)) { - tree_iterator__pop_all(ti, true, false); - return GIT_ITEROVER; - } - - ti->entry_is_current = true; - - return 0; -} - -static int tree_iterator__current_internal( - const git_index_entry **entry, git_iterator *self) -{ - int error; - tree_iterator *ti = (tree_iterator *)self; - tree_iterator_frame *tf = ti->head; - - iterator__clear_entry(entry); - - if (tf->current >= tf->n_entries) - return GIT_ITEROVER; - - if ((error = tree_iterator__update_entry(ti)) < 0) - return error; - - if (entry) - *entry = &ti->entry; - - ti->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - return 0; -} - -static int tree_iterator__advance_into_internal(git_iterator *self) -{ - int error = 0; - tree_iterator *ti = (tree_iterator *)self; - - if (tree_iterator__at_tree(ti)) - error = tree_iterator__push_frame(ti); - - return error; -} - -static int tree_iterator__advance_internal(git_iterator *self) -{ - int error; - tree_iterator *ti = (tree_iterator *)self; - tree_iterator_frame *tf = ti->head; - - if (tf->current >= tf->n_entries) - return GIT_ITEROVER; - - if (!iterator__has_been_accessed(ti)) - return 0; - - if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) && - tree_iterator__at_tree(ti)) - return tree_iterator__advance_into_internal(self); - - if (ti->path_has_filename) { - git_buf_rtruncate_at_char(&ti->path, '/'); - ti->path_has_filename = ti->entry_is_current = false; - } - - /* scan forward and up, advancing in frame or popping frame when done */ - while (!tree_iterator__move_to_next(ti, tf) && - tree_iterator__pop_frame(ti, false)) - tf = ti->head; - - /* find next and load trees */ - if ((error = tree_iterator__set_next(ti, tf)) < 0) - return error; - - /* deal with include_trees / auto_expand as needed */ - if (!iterator__include_trees(ti) && tree_iterator__at_tree(ti)) - return tree_iterator__advance_into_internal(self); - - return 0; -} - -static int tree_iterator__current( - const git_index_entry **out, git_iterator *self) -{ - const git_index_entry *entry = NULL; - iterator_pathlist__match_t m; - int error; - - do { - if ((error = tree_iterator__current_internal(&entry, self)) < 0) - return error; - - if (self->pathlist.length) { - m = iterator_pathlist__match( - self, entry->path, strlen(entry->path)); - - if (m != ITERATOR_PATHLIST_MATCH) { - if ((error = tree_iterator__advance_internal(self)) < 0) - return error; - - entry = NULL; - } - } - } while (!entry); - - if (out) - *out = entry; - - return error; -} - -static int tree_iterator__advance( - const git_index_entry **entry, git_iterator *self) -{ - int error = tree_iterator__advance_internal(self); - - iterator__clear_entry(entry); - - if (error < 0) - return error; - - return tree_iterator__current(entry, self); -} - -static int tree_iterator__advance_into( - const git_index_entry **entry, git_iterator *self) -{ - int error = tree_iterator__advance_into_internal(self); - - iterator__clear_entry(entry); - - if (error < 0) - return error; - - return tree_iterator__current(entry, self); -} - -static int tree_iterator__seek(git_iterator *self, const char *prefix) -{ - GIT_UNUSED(self); GIT_UNUSED(prefix); - return -1; -} - -static int tree_iterator__reset( - git_iterator *self, const char *start, const char *end) -{ - tree_iterator *ti = (tree_iterator *)self; - - tree_iterator__pop_all(ti, false, false); - - if (iterator__reset_range(self, start, end) < 0) + if (git_buf_oom(out)) return -1; - return tree_iterator__push_frame(ti); /* re-expand root tree */ + return 0; } -static int tree_iterator__at_end(git_iterator *self) +static int tree_iterator_frame_init( + tree_iterator *iter, + git_tree *tree, + tree_iterator_entry *frame_entry) { - tree_iterator *ti = (tree_iterator *)self; - return (ti->head->current >= ti->head->n_entries); -} + tree_iterator_frame *new_frame = NULL; + tree_iterator_entry *new_entry; + git_tree *dup = NULL; + git_tree_entry *tree_entry; + git_vector_cmp cmp; + size_t i; + int error = 0; -static void tree_iterator__free(git_iterator *self) -{ - tree_iterator *ti = (tree_iterator *)self; + new_frame = git_array_alloc(iter->frames); + GITERR_CHECK_ALLOC(new_frame); - if (ti->head) { - tree_iterator__pop_all(ti, true, false); - git_tree_free(ti->head->entries[0]->tree); - git__free(ti->head); + memset(new_frame, 0, sizeof(tree_iterator_frame)); + + if ((error = git_tree_dup(&dup, tree)) < 0) + goto done; + + memset(new_frame, 0x0, sizeof(tree_iterator_frame)); + new_frame->tree = dup; + + if (frame_entry && + (error = tree_iterator_compute_path(&new_frame->path, frame_entry)) < 0) + goto done; + + cmp = iterator__ignore_case(&iter->base) ? + tree_iterator_entry_sort_icase : NULL; + + if ((error = git_vector_init( + &new_frame->entries, dup->entries.size, cmp)) < 0) + goto done; + + git_array_foreach(dup->entries, i, tree_entry) { + new_entry = git_pool_malloc(&iter->entry_pool, 1); + GITERR_CHECK_ALLOC(new_entry); + + new_entry->tree_entry = tree_entry; + new_entry->parent_path = new_frame->path.ptr; + + if ((error = git_vector_insert(&new_frame->entries, new_entry)) < 0) + goto done; } - git_pool_clear(&ti->pool); - git_buf_free(&ti->path); -} + git_vector_set_sorted(&new_frame->entries, + !iterator__ignore_case(&iter->base)); -static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree) -{ - size_t sz = sizeof(tree_iterator_frame) + sizeof(tree_iterator_entry); - tree_iterator_frame *root = git__calloc(sz, sizeof(char)); - GITERR_CHECK_ALLOC(root); +done: + if (error < 0) { + git_tree_free(dup); + git_array_pop(iter->frames); + } - root->n_entries = 1; - root->next = 1; - root->start = ti->base.start; - root->startlen = root->start ? strlen(root->start) : 0; - root->entries[0] = git_pool_mallocz(&ti->pool, 1); - GITERR_CHECK_ALLOC(root->entries[0]); - root->entries[0]->tree = tree; - - ti->head = ti->root = root; - - return 0; -} - -int git_iterator_for_tree( - git_iterator **iter, - git_tree *tree, - git_iterator_options *options) -{ - int error; - tree_iterator *ti; - - if (tree == NULL) - return git_iterator_for_nothing(iter, options); - - if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0) - return error; - - ti = git__calloc(1, sizeof(tree_iterator)); - GITERR_CHECK_ALLOC(ti); - - ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree)); - - if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0) - goto fail; - - git_pool_init(&ti->pool, sizeof(tree_iterator_entry)); - - if ((error = tree_iterator__create_root_frame(ti, tree)) < 0 || - (error = tree_iterator__push_frame(ti)) < 0) /* expand root now */ - goto fail; - - *iter = (git_iterator *)ti; - return 0; - -fail: - git_iterator_free((git_iterator *)ti); return error; } - -typedef struct { - git_iterator base; - git_iterator_callbacks cb; - git_index *index; - git_vector entries; - git_vector_cmp entry_srch; - size_t current; - /* when limiting with a pathlist, this is the current index into it */ - size_t pathlist_idx; - /* when not in autoexpand mode, use these to represent "tree" state */ - git_buf partial; - size_t partial_pos; - char restore_terminator; - git_index_entry tree_entry; -} index_iterator; - -static const git_index_entry *index_iterator__index_entry(index_iterator *ii) +GIT_INLINE(tree_iterator_entry *) tree_iterator_current_entry( + tree_iterator_frame *frame) { - const git_index_entry *ie = git_vector_get(&ii->entries, ii->current); - - if (ie != NULL && iterator__past_end(ii, ie->path)) { - ii->current = git_vector_length(&ii->entries); - ie = NULL; - } - - return ie; + return frame->current; } -static const git_index_entry *index_iterator__advance_over_unwanted( - index_iterator *ii) +GIT_INLINE(int) tree_iterator_frame_push_neighbors( + tree_iterator *iter, + tree_iterator_frame *parent_frame, + tree_iterator_frame *frame, + const char *filename) { - const git_index_entry *ie = index_iterator__index_entry(ii); - bool match; + tree_iterator_entry *entry, *new_entry; + git_tree *tree = NULL; + git_tree_entry *tree_entry; + git_buf *path; + size_t new_size, i; + int error = 0; - while (ie) { - if (!iterator__include_conflicts(ii) && - git_index_entry_is_conflict(ie)) { - ii->current++; - ie = index_iterator__index_entry(ii); + while (parent_frame->next_idx < parent_frame->entries.length) { + entry = parent_frame->entries.contents[parent_frame->next_idx]; + + if (strcasecmp(filename, entry->tree_entry->filename) != 0) + break; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, entry->tree_entry->oid)) < 0) + break; + + if (git_vector_insert(&parent_frame->similar_trees, tree) < 0) + break; + + path = git_array_alloc(parent_frame->similar_paths); + GITERR_CHECK_ALLOC(path); + + memset(path, 0, sizeof(git_buf)); + + if ((error = tree_iterator_compute_path(path, entry)) < 0) + break; + + GITERR_CHECK_ALLOC_ADD(&new_size, + frame->entries.length, tree->entries.size); + git_vector_size_hint(&frame->entries, new_size); + + git_array_foreach(tree->entries, i, tree_entry) { + new_entry = git_pool_malloc(&iter->entry_pool, 1); + GITERR_CHECK_ALLOC(new_entry); + + new_entry->tree_entry = tree_entry; + new_entry->parent_path = path->ptr; + + if ((error = git_vector_insert(&frame->entries, new_entry)) < 0) + break; + } + + if (error) + break; + + parent_frame->next_idx++; + } + + return error; +} + +GIT_INLINE(int) tree_iterator_frame_push( + tree_iterator *iter, tree_iterator_entry *entry) +{ + tree_iterator_frame *parent_frame, *frame; + git_tree *tree = NULL; + int error; + + if ((error = git_tree_lookup(&tree, + iter->base.repo, entry->tree_entry->oid)) < 0 || + (error = tree_iterator_frame_init(iter, tree, entry)) < 0) + goto done; + + parent_frame = tree_iterator_parent_frame(iter); + frame = tree_iterator_current_frame(iter); + + /* if we're case insensitive, then we may have another directory that + * is (case insensitively) equal to this one. coalesce those children + * into this tree. + */ + if (iterator__ignore_case(&iter->base)) + error = tree_iterator_frame_push_neighbors(iter, + parent_frame, frame, entry->tree_entry->filename); + +done: + git_tree_free(tree); + return error; +} + +static void tree_iterator_frame_pop(tree_iterator *iter) +{ + tree_iterator_frame *frame; + git_buf *buf = NULL; + git_tree *tree; + size_t i; + + assert(iter->frames.size); + + frame = git_array_pop(iter->frames); + + git_vector_free(&frame->entries); + git_tree_free(frame->tree); + + do { + buf = git_array_pop(frame->similar_paths); + git_buf_free(buf); + } while (buf != NULL); + + git_array_clear(frame->similar_paths); + + git_vector_foreach(&frame->similar_trees, i, tree) + git_tree_free(tree); + + git_vector_free(&frame->similar_trees); + + git_buf_free(&frame->path); +} + +static int tree_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; + } + + *out = &iter->entry; + return 0; +} + +static void tree_iterator_set_current( + tree_iterator *iter, + tree_iterator_frame *frame, + tree_iterator_entry *entry) +{ + git_tree_entry *tree_entry = entry->tree_entry; + + frame->current = entry; + + memset(&iter->entry, 0x0, sizeof(git_index_entry)); + + iter->entry.mode = tree_entry->attr; + iter->entry.path = iter->entry_path.ptr; + git_oid_cpy(&iter->entry.id, tree_entry->oid); +} + +static int tree_iterator_advance(const git_index_entry **out, git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + /* examine tree entries until we find the next one to return */ + while (true) { + tree_iterator_entry *prev_entry, *entry; + tree_iterator_frame *frame; + bool is_tree; + + if ((frame = tree_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } + + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + tree_iterator_frame_pop(iter); continue; } - /* if we have a pathlist, this entry's path must be in it to be - * returned. walk the pathlist in unison with the index to - * compare paths. + /* we may have coalesced the contents of case-insensitively same-named + * directories, so do the sort now. */ - if (ii->base.pathlist.length) { - match = iterator_pathlist_walk__contains(&ii->base, ie->path); + if (frame->next_idx == 0 && !git_vector_is_sorted(&frame->entries)) + git_vector_sort(&frame->entries); - if (!match) { - ii->current++; - ie = index_iterator__index_entry(ii); - continue; - } + /* we have more entries in the current frame, that's our next entry */ + prev_entry = tree_iterator_current_entry(frame); + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; + + /* we can have collisions when iterating case insensitively. (eg, + * 'A/a' and 'a/A'). squash this one if it's already been seen. + */ + if (iterator__ignore_case(&iter->base) && + prev_entry && + tree_iterator_entry_cmp_icase(prev_entry, entry) == 0) + continue; + + if ((error = tree_iterator_compute_path(&iter->entry_path, entry)) < 0) + break; + + /* if this path is before our start, advance over this entry */ + if (!iterator_has_started(&iter->base, iter->entry_path.ptr, false)) + continue; + + /* if this path is after our end, stop */ + if (iterator_has_ended(&iter->base, iter->entry_path.ptr)) { + error = GIT_ITEROVER; + break; } + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, iter->entry_path.ptr)) + continue; + + is_tree = git_tree_entry__is_tree(entry->tree_entry); + + /* if we are *not* including trees then advance over this entry */ + if (is_tree && !iterator__include_trees(iter)) { + + /* if we've found a tree (and are not returning it to the caller) + * and we are autoexpanding, then we want to return the first + * child. push the new directory and advance. + */ + if (iterator__do_autoexpand(iter)) { + if ((error = tree_iterator_frame_push(iter, entry)) < 0) + break; + } + + continue; + } + + tree_iterator_set_current(iter, frame, entry); + + /* if we are autoexpanding, then push this as a new frame, so that + * the next call to `advance` will dive into this directory. + */ + if (is_tree && iterator__do_autoexpand(iter)) + error = tree_iterator_frame_push(iter, entry); + break; } - return ie; + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; } -static void index_iterator__next_prefix_tree(index_iterator *ii) +static int tree_iterator_advance_into( + const git_index_entry **out, git_iterator *i) { - const char *slash; + tree_iterator *iter = (tree_iterator *)i; + tree_iterator_frame *frame; + tree_iterator_entry *prev_entry; + int error; - if (!iterator__include_trees(ii)) - return; + if (out) + *out = NULL; - slash = strchr(&ii->partial.ptr[ii->partial_pos], '/'); + if ((frame = tree_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; - if (slash != NULL) { - ii->partial_pos = (slash - ii->partial.ptr) + 1; - ii->restore_terminator = ii->partial.ptr[ii->partial_pos]; - ii->partial.ptr[ii->partial_pos] = '\0'; - } else { - ii->partial_pos = ii->partial.size; - } + /* get the last seen entry */ + prev_entry = tree_iterator_current_entry(frame); - if (index_iterator__index_entry(ii) == NULL) - ii->partial_pos = ii->partial.size; -} - -static int index_iterator__first_prefix_tree(index_iterator *ii) -{ - const git_index_entry *ie = index_iterator__advance_over_unwanted(ii); - const char *scan, *prior, *slash; - - if (!ie || !iterator__include_trees(ii)) - return 0; - - /* find longest common prefix with prior index entry */ - for (scan = slash = ie->path, prior = ii->partial.ptr; - *scan && *scan == *prior; ++scan, ++prior) - if (*scan == '/') - slash = scan; - - if (git_buf_sets(&ii->partial, ie->path) < 0) - return -1; - - ii->partial_pos = (slash - ie->path) + 1; - index_iterator__next_prefix_tree(ii); - - return 0; -} - -#define index_iterator__at_tree(I) \ - (iterator__include_trees(I) && (I)->partial_pos < (I)->partial.size) - -static int index_iterator__current( - const git_index_entry **entry, git_iterator *self) -{ - index_iterator *ii = (index_iterator *)self; - const git_index_entry *ie = git_vector_get(&ii->entries, ii->current); - - if (ie != NULL && index_iterator__at_tree(ii)) { - ii->tree_entry.path = ii->partial.ptr; - ie = &ii->tree_entry; - } - - if (entry) - *entry = ie; - - ii->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - return (ie != NULL) ? 0 : GIT_ITEROVER; -} - -static int index_iterator__at_end(git_iterator *self) -{ - index_iterator *ii = (index_iterator *)self; - return (ii->current >= git_vector_length(&ii->entries)); -} - -static int index_iterator__advance( - const git_index_entry **entry, git_iterator *self) -{ - index_iterator *ii = (index_iterator *)self; - size_t entrycount = git_vector_length(&ii->entries); - const git_index_entry *ie; - - if (!iterator__has_been_accessed(ii)) - return index_iterator__current(entry, self); - - if (index_iterator__at_tree(ii)) { - if (iterator__do_autoexpand(ii)) { - ii->partial.ptr[ii->partial_pos] = ii->restore_terminator; - index_iterator__next_prefix_tree(ii); - } else { - /* advance to sibling tree (i.e. find entry with new prefix) */ - while (ii->current < entrycount) { - ii->current++; - - if (!(ie = git_vector_get(&ii->entries, ii->current)) || - ii->base.prefixcomp(ie->path, ii->partial.ptr) != 0) - break; - } - - if (index_iterator__first_prefix_tree(ii) < 0) - return -1; - } - } else { - if (ii->current < entrycount) - ii->current++; - - if (index_iterator__first_prefix_tree(ii) < 0) - return -1; - } - - return index_iterator__current(entry, self); -} - -static int index_iterator__advance_into( - const git_index_entry **entry, git_iterator *self) -{ - index_iterator *ii = (index_iterator *)self; - const git_index_entry *ie = git_vector_get(&ii->entries, ii->current); - - if (ie != NULL && index_iterator__at_tree(ii)) { - if (ii->restore_terminator) - ii->partial.ptr[ii->partial_pos] = ii->restore_terminator; - index_iterator__next_prefix_tree(ii); - } - - return index_iterator__current(entry, self); -} - -static int index_iterator__seek(git_iterator *self, const char *prefix) -{ - GIT_UNUSED(self); GIT_UNUSED(prefix); - return -1; -} - -static int index_iterator__reset( - git_iterator *self, const char *start, const char *end) -{ - index_iterator *ii = (index_iterator *)self; - const git_index_entry *ie; - - if (iterator__reset_range(self, start, end) < 0) - return -1; - - ii->current = 0; - - iterator_pathlist_walk__reset(self); - - /* if we're given a start prefix, find it; if we're given a pathlist, find - * the first of those. start at the later of the two. + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. */ - if (ii->base.start) - git_index_snapshot_find( - &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0); + assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); - if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL) - return 0; + if (prev_entry) { + if (!git_tree_entry__is_tree(prev_entry->tree_entry)) + return 0; - if (git_buf_sets(&ii->partial, ie->path) < 0) - return -1; - - ii->partial_pos = 0; - - if (ii->base.start) { - size_t startlen = strlen(ii->base.start); - - ii->partial_pos = (startlen > ii->partial.size) ? - ii->partial.size : startlen; + if ((error = tree_iterator_frame_push(iter, prev_entry)) < 0) + return error; } - index_iterator__next_prefix_tree(ii); + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return tree_iterator_advance(out, i); +} + +static int tree_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + *status = GIT_ITERATOR_STATUS_NORMAL; + return git_iterator_advance(out, i); +} + +static void tree_iterator_clear(tree_iterator *iter) +{ + while (iter->frames.size) + tree_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + + git_pool_clear(&iter->entry_pool); + git_buf_clear(&iter->entry_path); + + iterator_clear(&iter->base); +} + +static int tree_iterator_init(tree_iterator *iter) +{ + int error; + + git_pool_init(&iter->entry_pool, sizeof(tree_iterator_entry)); + + if ((error = tree_iterator_frame_init(iter, iter->root, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; return 0; } -static void index_iterator__free(git_iterator *self) +static int tree_iterator_reset(git_iterator *i) { - index_iterator *ii = (index_iterator *)self; - git_index_snapshot_release(&ii->entries, ii->index); - ii->index = NULL; - git_buf_free(&ii->partial); + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + return tree_iterator_init(iter); } -int git_iterator_for_index( - git_iterator **iter, - git_repository *repo, - git_index *index, +static void tree_iterator_free(git_iterator *i) +{ + tree_iterator *iter = (tree_iterator *)i; + + tree_iterator_clear(iter); + + git_tree_free(iter->root); + git_buf_free(&iter->entry_path); +} + +int git_iterator_for_tree( + git_iterator **out, + git_tree *tree, git_iterator_options *options) { - int error = 0; - index_iterator *ii = git__calloc(1, sizeof(index_iterator)); - GITERR_CHECK_ALLOC(ii); + tree_iterator *iter; + int error; - if ((error = git_index_snapshot_new(&ii->entries, index)) < 0) { - git__free(ii); - return error; - } - ii->index = index; + static git_iterator_callbacks callbacks = { + tree_iterator_current, + tree_iterator_advance, + tree_iterator_advance_into, + tree_iterator_advance_over, + tree_iterator_reset, + tree_iterator_free + }; - ITERATOR_BASE_INIT(ii, index, INDEX, repo); + *out = NULL; - if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) { - git_iterator_free((git_iterator *)ii); - return error; - } + if (tree == NULL) + return git_iterator_for_nothing(out, options); - ii->entry_srch = iterator__ignore_case(ii) ? - git_index_entry_isrch : git_index_entry_srch; + iter = git__calloc(1, sizeof(tree_iterator)); + GITERR_CHECK_ALLOC(iter); - git_vector_set_cmp(&ii->entries, iterator__ignore_case(ii) ? - git_index_entry_icmp : git_index_entry_cmp); - git_vector_sort(&ii->entries); + iter->base.type = GIT_ITERATOR_TYPE_TREE; + iter->base.cb = &callbacks; - git_buf_init(&ii->partial, 0); - ii->tree_entry.mode = GIT_FILEMODE_TREE; + if ((error = iterator_init_common(&iter->base, + git_tree_owner(tree), NULL, options)) < 0 || + (error = git_tree_dup(&iter->root, tree)) < 0 || + (error = tree_iterator_init(iter)) < 0) + goto on_error; - index_iterator__reset((git_iterator *)ii, NULL, NULL); + *out = &iter->base; + return 0; - *iter = (git_iterator *)ii; +on_error: + git_iterator_free(&iter->base); + return error; +} + +int git_iterator_current_tree_entry( + const git_tree_entry **tree_entry, git_iterator *i) +{ + tree_iterator *iter; + tree_iterator_frame *frame; + tree_iterator_entry *entry; + + assert(i->type == GIT_ITERATOR_TYPE_TREE); + + iter = (tree_iterator *)i; + + frame = tree_iterator_current_frame(iter); + entry = tree_iterator_current_entry(frame); + + *tree_entry = entry->tree_entry; return 0; } +int git_iterator_current_parent_tree( + const git_tree **parent_tree, git_iterator *i, size_t depth) +{ + tree_iterator *iter; + tree_iterator_frame *frame; -typedef struct fs_iterator_frame fs_iterator_frame; -struct fs_iterator_frame { - fs_iterator_frame *next; - git_vector entries; - size_t index; - int is_ignored; -}; + assert(i->type == GIT_ITERATOR_TYPE_TREE); -typedef struct fs_iterator fs_iterator; -struct fs_iterator { - git_iterator base; - git_iterator_callbacks cb; - fs_iterator_frame *stack; - git_index_entry entry; - git_buf path; - size_t root_len; - uint32_t dirload_flags; - int depth; - iterator_pathlist__match_t pathlist_match; + iter = (tree_iterator *)i; - int (*enter_dir_cb)(fs_iterator *self); - int (*leave_dir_cb)(fs_iterator *self); - int (*update_entry_cb)(fs_iterator *self); -}; + assert(depth < iter->frames.size); + frame = &iter->frames.ptr[iter->frames.size-depth-1]; -#define FS_MAX_DEPTH 100 + *parent_tree = frame->tree; + return 0; +} + +/* Filesystem iterator */ typedef struct { struct stat st; - iterator_pathlist__match_t pathlist_match; - size_t path_len; - char path[GIT_FLEX_ARRAY]; -} fs_iterator_path_with_stat; + size_t path_len; + iterator_pathlist_search_t match; + char path[GIT_FLEX_ARRAY]; +} filesystem_iterator_entry; -static int fs_iterator_path_with_stat_cmp(const void *a, const void *b) +typedef struct { + git_vector entries; + git_pool entry_pool; + size_t next_idx; + + size_t path_len; + int is_ignored; +} filesystem_iterator_frame; + +typedef struct { + git_iterator base; + char *root; + size_t root_len; + + unsigned int dirload_flags; + + git_tree *tree; + git_index *index; + git_vector index_snapshot; + + git_array_t(filesystem_iterator_frame) frames; + git_ignores ignores; + + /* info about the current entry */ + git_index_entry entry; + git_buf current_path; + int current_is_ignored; + + /* temporary buffer for advance_over */ + git_buf tmp_buf; +} filesystem_iterator; + + +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_parent_frame( + filesystem_iterator *iter) { - const fs_iterator_path_with_stat *psa = a, *psb = b; - return strcmp(psa->path, psb->path); + return iter->frames.size > 1 ? + &iter->frames.ptr[iter->frames.size-2] : NULL; } -static int fs_iterator_path_with_stat_cmp_icase(const void *a, const void *b) +GIT_INLINE(filesystem_iterator_frame *) filesystem_iterator_current_frame( + filesystem_iterator *iter) { - const fs_iterator_path_with_stat *psa = a, *psb = b; - return strcasecmp(psa->path, psb->path); + return iter->frames.size ? &iter->frames.ptr[iter->frames.size-1] : NULL; } -static fs_iterator_frame *fs_iterator__alloc_frame(fs_iterator *fi) +GIT_INLINE(filesystem_iterator_entry *) filesystem_iterator_current_entry( + filesystem_iterator_frame *frame) { - fs_iterator_frame *ff = git__calloc(1, sizeof(fs_iterator_frame)); - git_vector_cmp entry_compare = CASESELECT( - iterator__ignore_case(fi), - fs_iterator_path_with_stat_cmp_icase, - fs_iterator_path_with_stat_cmp); - - if (ff && git_vector_init(&ff->entries, 0, entry_compare) < 0) { - git__free(ff); - ff = NULL; - } - - return ff; + return frame->next_idx == 0 ? + NULL : frame->entries.contents[frame->next_idx-1]; } -static void fs_iterator__free_frame(fs_iterator_frame *ff) +static int filesystem_iterator_entry_cmp(const void *_a, const void *_b) { - git_vector_free_deep(&ff->entries); - git__free(ff); + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcmp(a->path, b->path); } -static void fs_iterator__pop_frame( - fs_iterator *fi, fs_iterator_frame *ff, bool pop_last) +static int filesystem_iterator_entry_cmp_icase(const void *_a, const void *_b) { - if (fi && fi->stack == ff) { - if (!ff->next && !pop_last) { - memset(&fi->entry, 0, sizeof(fi->entry)); - return; + const filesystem_iterator_entry *a = (const filesystem_iterator_entry *)_a; + const filesystem_iterator_entry *b = (const filesystem_iterator_entry *)_b; + + return git__strcasecmp(a->path, b->path); +} + +#define FILESYSTEM_MAX_DEPTH 100 + +/** + * Figure out if an entry is a submodule. + * + * We consider it a submodule if the path is listed as a submodule in + * either the tree or the index. + */ +static int filesystem_iterator_is_submodule( + bool *out, filesystem_iterator *iter, const char *path, size_t path_len) +{ + bool is_submodule = false; + int error; + + *out = false; + + /* first see if this path is a submodule in HEAD */ + if (iter->tree) { + git_tree_entry *entry; + + error = git_tree_entry_bypath(&entry, iter->tree, path); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + is_submodule = (entry->attr == GIT_FILEMODE_COMMIT); + git_tree_entry_free(entry); } - - if (fi->leave_dir_cb) - (void)fi->leave_dir_cb(fi); - - fi->stack = ff->next; - fi->depth--; } - fs_iterator__free_frame(ff); + if (!is_submodule && iter->base.index) { + size_t pos; + + error = git_index_snapshot_find(&pos, + &iter->index_snapshot, iter->base.entry_srch, path, path_len, 0); + + if (error < 0 && error != GIT_ENOTFOUND) + return error; + + if (!error) { + git_index_entry *e = git_vector_get(&iter->index_snapshot, pos); + is_submodule = (e->mode == GIT_FILEMODE_COMMIT); + } + } + + *out = is_submodule; + return 0; } -static int fs_iterator__update_entry(fs_iterator *fi); -static int fs_iterator__advance_over( - const git_index_entry **entry, git_iterator *self); - -static int fs_iterator__entry_cmp(const void *i, const void *item) +static void filesystem_iterator_frame_push_ignores( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + filesystem_iterator_frame *new_frame) { - const fs_iterator *fi = (const fs_iterator *)i; - const fs_iterator_path_with_stat *ps = item; - return fi->base.prefixcomp(fi->base.start, ps->path); -} + filesystem_iterator_frame *previous_frame; + const char *path = frame_entry ? frame_entry->path : ""; -static void fs_iterator__seek_frame_start( - fs_iterator *fi, fs_iterator_frame *ff) -{ - if (!ff) + if (!iterator__honor_ignores(&iter->base)) return; - if (fi->base.start) - git_vector_bsearch2( - &ff->index, &ff->entries, fs_iterator__entry_cmp, fi); - else - ff->index = 0; + if (git_ignore__lookup(&new_frame->is_ignored, + &iter->ignores, path, GIT_DIR_FLAG_TRUE) < 0) { + giterr_clear(); + new_frame->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ + if (frame_entry) { + const char *relative_path; + + previous_frame = filesystem_iterator_parent_frame(iter); + + /* push new ignores for files in this directory */ + relative_path = frame_entry->path + previous_frame->path_len; + + /* inherit ignored from parent if no rule specified */ + if (new_frame->is_ignored <= GIT_IGNORE_NOTFOUND) + new_frame->is_ignored = previous_frame->is_ignored; + + git_ignore__push_dir(&iter->ignores, relative_path); + } } -static int dirload_with_stat(git_vector *contents, fs_iterator *fi) +static void filesystem_iterator_frame_pop_ignores( + filesystem_iterator *iter) { + if (iterator__honor_ignores(&iter->base)) + git_ignore__pop_dir(&iter->ignores); +} + +GIT_INLINE(bool) filesystem_iterator_examine_path( + bool *is_dir_out, + iterator_pathlist_search_t *match_out, + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry, + const char *path, + size_t path_len) +{ + bool is_dir = 0; + iterator_pathlist_search_t match = ITERATOR_PATHLIST_FULL; + + *is_dir_out = false; + *match_out = ITERATOR_PATHLIST_NONE; + + if (iter->base.start_len) { + int cmp = iter->base.strncomp(path, iter->base.start, path_len); + + /* we haven't stat'ed `path` yet, so we don't yet know if it's a + * directory or not. special case if the current path may be a + * directory that matches the start prefix. + */ + if (cmp == 0) { + if (iter->base.start[path_len] == '/') + is_dir = true; + + else if (iter->base.start[path_len] != '\0') + cmp = -1; + } + + if (cmp < 0) + return false; + } + + if (iter->base.end_len) { + int cmp = iter->base.strncomp(path, iter->base.end, iter->base.end_len); + + if (cmp > 0) + return false; + } + + /* if we have a pathlist that we're limiting to, examine this path now + * to avoid a `stat` if we're not interested in the path. + */ + if (iter->base.pathlist.length) { + /* if our parent was explicitly included, so too are we */ + if (frame_entry && frame_entry->match != ITERATOR_PATHLIST_IS_PARENT) + match = ITERATOR_PATHLIST_FULL; + else + match = iterator_pathlist_search(&iter->base, path, path_len); + + if (match == ITERATOR_PATHLIST_NONE) + return false; + + /* Ensure that the pathlist entry lines up with what we expected */ + if (match == ITERATOR_PATHLIST_IS_DIR || + match == ITERATOR_PATHLIST_IS_PARENT) + is_dir = true; + } + + *is_dir_out = is_dir; + *match_out = match; + return true; +} + +GIT_INLINE(bool) filesystem_iterator_is_dot_git( + filesystem_iterator *iter, const char *path, size_t path_len) +{ + size_t len; + + if (!iterator__ignore_dot_git(&iter->base)) + return false; + + if ((len = path_len) < 4) + return false; + + if (path[len - 1] == '/') + len--; + + if (git__tolower(path[len - 1]) != 't' || + git__tolower(path[len - 2]) != 'i' || + git__tolower(path[len - 3]) != 'g' || + git__tolower(path[len - 4]) != '.') + return false; + + return (len == 4 || path[len - 5] == '/'); +} + +static filesystem_iterator_entry *filesystem_iterator_entry_init( + filesystem_iterator_frame *frame, + const char *path, + size_t path_len, + struct stat *statbuf, + iterator_pathlist_search_t pathlist_match) +{ + filesystem_iterator_entry *entry; + size_t entry_size; + + /* Make sure to append two bytes, one for the path's null + * termination, one for a possible trailing '/' for folders. + */ + if (GIT_ADD_SIZET_OVERFLOW(&entry_size, + sizeof(filesystem_iterator_entry), path_len) || + GIT_ADD_SIZET_OVERFLOW(&entry_size, entry_size, 2) || + (entry = git_pool_malloc(&frame->entry_pool, entry_size)) == NULL) + return NULL; + + entry->path_len = path_len; + entry->match = pathlist_match; + memcpy(entry->path, path, path_len); + memcpy(&entry->st, statbuf, sizeof(struct stat)); + + /* Suffix directory paths with a '/' */ + if (S_ISDIR(entry->st.st_mode)) + entry->path[entry->path_len++] = '/'; + + entry->path[entry->path_len] = '\0'; + + return entry; +} + +static int filesystem_iterator_frame_push( + filesystem_iterator *iter, + filesystem_iterator_entry *frame_entry) +{ + filesystem_iterator_frame *new_frame = NULL; git_path_diriter diriter = GIT_PATH_DIRITER_INIT; + git_buf root = GIT_BUF_INIT; const char *path; - size_t start_len = fi->base.start ? strlen(fi->base.start) : 0; - size_t end_len = fi->base.end ? strlen(fi->base.end) : 0; - fs_iterator_path_with_stat *ps; - size_t path_len, cmp_len, ps_size; - iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH; + filesystem_iterator_entry *entry; + struct stat statbuf; + size_t path_len; int error; + if (iter->frames.size == FILESYSTEM_MAX_DEPTH) { + giterr_set(GITERR_REPOSITORY, + "directory nesting too deep (%"PRIuZ")", iter->frames.size); + return -1; + } + + new_frame = git_array_alloc(iter->frames); + GITERR_CHECK_ALLOC(new_frame); + + memset(new_frame, 0, sizeof(filesystem_iterator_frame)); + + if (frame_entry) + git_buf_joinpath(&root, iter->root, frame_entry->path); + else + git_buf_puts(&root, iter->root); + + if (git_buf_oom(&root)) { + error = -1; + goto done; + } + + new_frame->path_len = frame_entry ? frame_entry->path_len : 0; + /* Any error here is equivalent to the dir not existing, skip over it */ if ((error = git_path_diriter_init( - &diriter, fi->path.ptr, fi->dirload_flags)) < 0) { + &diriter, root.ptr, iter->dirload_flags)) < 0) { error = GIT_ENOTFOUND; goto done; } + if ((error = git_vector_init(&new_frame->entries, 64, + iterator__ignore_case(&iter->base) ? + filesystem_iterator_entry_cmp_icase : + filesystem_iterator_entry_cmp)) < 0) + goto done; + + git_pool_init(&new_frame->entry_pool, 1); + + /* check if this directory is ignored */ + filesystem_iterator_frame_push_ignores(iter, frame_entry, new_frame); + while ((error = git_path_diriter_next(&diriter)) == 0) { + iterator_pathlist_search_t pathlist_match = ITERATOR_PATHLIST_FULL; + bool dir_expected = false; + if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) goto done; - assert(path_len > fi->root_len); + assert(path_len > iter->root_len); /* remove the prefix if requested */ - path += fi->root_len; - path_len -= fi->root_len; + path += iter->root_len; + path_len -= iter->root_len; - /* skip if before start_stat or after end_stat */ - cmp_len = min(start_len, path_len); - if (cmp_len && fi->base.strncomp(path, fi->base.start, cmp_len) < 0) - continue; - /* skip if after end_stat */ - cmp_len = min(end_len, path_len); - if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0) - continue; - - /* if we have a pathlist that we're limiting to, examine this path. - * if the frame has already deemed us inside the path (eg, we're in - * `foo/bar` and the pathlist previously was detected to say `foo/`) - * then simply continue. otherwise, examine the pathlist looking for - * this path or children of this path. + /* examine start / end and the pathlist to see if this path is in it. + * note that since we haven't yet stat'ed the path, we cannot know + * whether it's a directory yet or not, so this can give us an + * expected type (S_IFDIR or S_IFREG) that we should examine) */ - if (fi->base.pathlist.length && - fi->pathlist_match != ITERATOR_PATHLIST_MATCH && - fi->pathlist_match != ITERATOR_PATHLIST_MATCH_DIRECTORY && - !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len))) + if (!filesystem_iterator_examine_path(&dir_expected, &pathlist_match, + iter, frame_entry, path, path_len)) continue; - /* Make sure to append two bytes, one for the path's null - * termination, one for a possible trailing '/' for folders. + /* TODO: don't need to stat if assume unchanged for this path and + * we have an index, we can just copy the data out of it. */ - GITERR_CHECK_ALLOC_ADD(&ps_size, sizeof(fs_iterator_path_with_stat), path_len); - GITERR_CHECK_ALLOC_ADD(&ps_size, ps_size, 2); - ps = git__calloc(1, ps_size); - ps->path_len = path_len; - - memcpy(ps->path, path, path_len); - - /* TODO: don't stat if assume unchanged for this path */ - - if ((error = git_path_diriter_stat(&ps->st, &diriter)) < 0) { - if (error == GIT_ENOTFOUND) { - /* file was removed between readdir and lstat */ - git__free(ps); + if ((error = git_path_diriter_stat(&statbuf, &diriter)) < 0) { + /* file was removed between readdir and lstat */ + if (error == GIT_ENOTFOUND) continue; - } - if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) { - /* were looking for a directory, but this is a file */ - git__free(ps); - continue; - } + /* treat the file as unreadable */ + memset(&statbuf, 0, sizeof(statbuf)); + statbuf.st_mode = GIT_FILEMODE_UNREADABLE; - /* Treat the file as unreadable if we get any other error */ - memset(&ps->st, 0, sizeof(ps->st)); - ps->st.st_mode = GIT_FILEMODE_UNREADABLE; - - giterr_clear(); error = 0; - } else if (S_ISDIR(ps->st.st_mode)) { - /* Suffix directory paths with a '/' */ - ps->path[ps->path_len++] = '/'; - ps->path[ps->path_len] = '\0'; - } else if(!S_ISREG(ps->st.st_mode) && !S_ISLNK(ps->st.st_mode)) { - /* Ignore wacky things in the filesystem */ - git__free(ps); - continue; } - /* record whether this path was explicitly found in the path list - * or whether we're only examining it because something beneath it - * is in the path list. - */ - ps->pathlist_match = pathlist_match; - git_vector_insert(contents, ps); + iter->base.stat_calls++; + + /* Ignore wacky things in the filesystem */ + if (!S_ISDIR(statbuf.st_mode) && + !S_ISREG(statbuf.st_mode) && + !S_ISLNK(statbuf.st_mode) && + statbuf.st_mode != GIT_FILEMODE_UNREADABLE) + continue; + + if (filesystem_iterator_is_dot_git(iter, path, path_len)) + continue; + + /* convert submodules to GITLINK and remove trailing slashes */ + if (S_ISDIR(statbuf.st_mode)) { + bool submodule = false; + + if ((error = filesystem_iterator_is_submodule(&submodule, + iter, path, path_len)) < 0) + goto done; + + if (submodule) + statbuf.st_mode = GIT_FILEMODE_COMMIT; + } + + /* Ensure that the pathlist entry lines up with what we expected */ + else if (dir_expected) + continue; + + entry = filesystem_iterator_entry_init(new_frame, + path, path_len, &statbuf, pathlist_match); + GITERR_CHECK_ALLOC(entry); + + git_vector_insert(&new_frame->entries, entry); } if (error == GIT_ITEROVER) error = 0; /* sort now that directory suffix is added */ - git_vector_sort(contents); + git_vector_sort(&new_frame->entries); done: + if (error < 0) + git_array_pop(iter->frames); + + git_buf_free(&root); git_path_diriter_free(&diriter); return error; } - -static int fs_iterator__expand_dir(fs_iterator *fi) +GIT_INLINE(void) filesystem_iterator_frame_pop(filesystem_iterator *iter) { - int error; - fs_iterator_frame *ff; + filesystem_iterator_frame *frame; - if (fi->depth > FS_MAX_DEPTH) { - giterr_set(GITERR_REPOSITORY, - "Directory nesting is too deep (%d)", fi->depth); - return -1; + assert(iter->frames.size); + + frame = git_array_pop(iter->frames); + filesystem_iterator_frame_pop_ignores(iter); + + git_pool_clear(&frame->entry_pool); + git_vector_free(&frame->entries); +} + +static void filesystem_iterator_set_current( + filesystem_iterator *iter, + filesystem_iterator_entry *entry) +{ + iter->entry.ctime.seconds = entry->st.st_ctime; + iter->entry.ctime.nanoseconds = entry->st.st_ctime_nsec; + + iter->entry.mtime.seconds = entry->st.st_mtime; + iter->entry.mtime.nanoseconds = entry->st.st_mtime_nsec; + + iter->entry.dev = entry->st.st_dev; + iter->entry.ino = entry->st.st_ino; + iter->entry.mode = git_futils_canonical_mode(entry->st.st_mode); + iter->entry.uid = entry->st.st_uid; + iter->entry.gid = entry->st.st_gid; + iter->entry.file_size = entry->st.st_size; + + iter->entry.path = entry->path; + + iter->current_is_ignored = GIT_IGNORE_UNCHECKED; +} + +static int filesystem_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (!iter->frames.size) { + *out = NULL; + return GIT_ITEROVER; } - ff = fs_iterator__alloc_frame(fi); - GITERR_CHECK_ALLOC(ff); - - error = dirload_with_stat(&ff->entries, fi); - - if (error < 0) { - git_error_state last_error = { 0 }; - giterr_state_capture(&last_error, error); - - /* these callbacks may clear the error message */ - fs_iterator__free_frame(ff); - fs_iterator__advance_over(NULL, (git_iterator *)fi); - /* next time return value we skipped to */ - fi->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; - - return giterr_state_restore(&last_error); - } - - if (ff->entries.length == 0) { - fs_iterator__free_frame(ff); - return GIT_ENOTFOUND; - } - fi->base.stat_calls += ff->entries.length; - - fs_iterator__seek_frame_start(fi, ff); - - ff->next = fi->stack; - fi->stack = ff; - fi->depth++; - - if (fi->enter_dir_cb && (error = fi->enter_dir_cb(fi)) < 0) - return error; - - return fs_iterator__update_entry(fi); -} - -static int fs_iterator__current( - const git_index_entry **entry, git_iterator *self) -{ - fs_iterator *fi = (fs_iterator *)self; - const git_index_entry *fe = (fi->entry.path == NULL) ? NULL : &fi->entry; - - if (entry) - *entry = fe; - - fi->base.flags |= GIT_ITERATOR_FIRST_ACCESS; - - return (fe != NULL) ? 0 : GIT_ITEROVER; -} - -static int fs_iterator__at_end(git_iterator *self) -{ - return (((fs_iterator *)self)->entry.path == NULL); -} - -static int fs_iterator__advance_into( - const git_index_entry **entry, git_iterator *iter) -{ - int error = 0; - fs_iterator *fi = (fs_iterator *)iter; - - iterator__clear_entry(entry); - - /* Allow you to explicitly advance into a commit/submodule (as well as a - * tree) to avoid cases where an entry is mislabeled as a submodule in - * the working directory. The fs iterator will never have COMMMIT - * entries on it's own, but a wrapper might add them. - */ - if (fi->entry.path != NULL && - (fi->entry.mode == GIT_FILEMODE_TREE || - fi->entry.mode == GIT_FILEMODE_COMMIT)) - /* returns GIT_ENOTFOUND if the directory is empty */ - error = fs_iterator__expand_dir(fi); - - if (!error && entry) - error = fs_iterator__current(entry, iter); - - if (!error && !fi->entry.path) - error = GIT_ITEROVER; - - return error; -} - -static void fs_iterator__advance_over_internal(git_iterator *self) -{ - fs_iterator *fi = (fs_iterator *)self; - fs_iterator_frame *ff; - fs_iterator_path_with_stat *next; - - while (fi->entry.path != NULL) { - ff = fi->stack; - next = git_vector_get(&ff->entries, ++ff->index); - - if (next != NULL) - break; - - fs_iterator__pop_frame(fi, ff, false); - } -} - -static int fs_iterator__advance_over( - const git_index_entry **entry, git_iterator *self) -{ - int error; - - if (entry != NULL) - *entry = NULL; - - fs_iterator__advance_over_internal(self); - - error = fs_iterator__update_entry((fs_iterator *)self); - - if (!error && entry != NULL) - error = fs_iterator__current(entry, self); - - return error; -} - -static int fs_iterator__advance( - const git_index_entry **entry, git_iterator *self) -{ - fs_iterator *fi = (fs_iterator *)self; - - if (!iterator__has_been_accessed(fi)) - return fs_iterator__current(entry, self); - - /* given include_trees & autoexpand, we might have to go into a tree */ - if (iterator__do_autoexpand(fi) && - fi->entry.path != NULL && - fi->entry.mode == GIT_FILEMODE_TREE) - { - int error = fs_iterator__advance_into(entry, self); - if (error != GIT_ENOTFOUND) - return error; - /* continue silently past empty directories if autoexpanding */ - giterr_clear(); - } - - return fs_iterator__advance_over(entry, self); -} - -static int fs_iterator__seek(git_iterator *self, const char *prefix) -{ - GIT_UNUSED(self); - GIT_UNUSED(prefix); - /* pop stack until matching prefix */ - /* find prefix item in current frame */ - /* push subdirectories as deep as possible while matching */ + *out = &iter->entry; return 0; } -static int fs_iterator__reset( - git_iterator *self, const char *start, const char *end) +static int filesystem_iterator_advance( + const git_index_entry **out, git_iterator *i) { - int error; - fs_iterator *fi = (fs_iterator *)self; + filesystem_iterator *iter = (filesystem_iterator *)i; + int error = 0; - while (fi->stack != NULL && fi->stack->next != NULL) - fs_iterator__pop_frame(fi, fi->stack, false); - fi->depth = 0; - - if ((error = iterator__reset_range(self, start, end)) < 0) - return error; - - fs_iterator__seek_frame_start(fi, fi->stack); - - error = fs_iterator__update_entry(fi); - if (error == GIT_ITEROVER) - error = 0; - - return error; -} - -static void fs_iterator__free(git_iterator *self) -{ - fs_iterator *fi = (fs_iterator *)self; - - while (fi->stack != NULL) - fs_iterator__pop_frame(fi, fi->stack, true); - - git_buf_free(&fi->path); -} - -static int fs_iterator__update_entry(fs_iterator *fi) -{ - fs_iterator_path_with_stat *ps; + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + /* examine filesystem entries until we find the next one to return */ while (true) { - memset(&fi->entry, 0, sizeof(fi->entry)); + filesystem_iterator_frame *frame; + filesystem_iterator_entry *entry; - if (!fi->stack) - return GIT_ITEROVER; + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) { + error = GIT_ITEROVER; + break; + } - ps = git_vector_get(&fi->stack->entries, fi->stack->index); - if (!ps) - return GIT_ITEROVER; - - git_buf_truncate(&fi->path, fi->root_len); - if (git_buf_put(&fi->path, ps->path, ps->path_len) < 0) - return -1; - - if (iterator__past_end(fi, fi->path.ptr + fi->root_len)) - return GIT_ITEROVER; - - fi->entry.path = ps->path; - fi->pathlist_match = ps->pathlist_match; - git_index_entry__init_from_stat(&fi->entry, &ps->st, true); - - /* need different mode here to keep directories during iteration */ - fi->entry.mode = git_futils_canonical_mode(ps->st.st_mode); - - /* allow wrapper to check/update the entry (can force skip) */ - if (fi->update_entry_cb && - fi->update_entry_cb(fi) == GIT_ENOTFOUND) { - fs_iterator__advance_over_internal(&fi->base); + /* no more entries in this frame. pop the frame out */ + if (frame->next_idx == frame->entries.length) { + filesystem_iterator_frame_pop(iter); continue; } - /* if this is a tree and trees aren't included, then skip */ - if (fi->entry.mode == GIT_FILEMODE_TREE && !iterator__include_trees(fi)) { - int error = fs_iterator__advance_into(NULL, &fi->base); + /* we have more entries in the current frame, that's our next entry */ + entry = frame->entries.contents[frame->next_idx]; + frame->next_idx++; - if (error != GIT_ENOTFOUND) - return error; + if (S_ISDIR(entry->st.st_mode)) { + if (iterator__do_autoexpand(iter)) { + error = filesystem_iterator_frame_push(iter, entry); - giterr_clear(); - fs_iterator__advance_over_internal(&fi->base); - continue; + /* may get GIT_ENOTFOUND due to races or permission problems + * that we want to quietly swallow + */ + if (error == GIT_ENOTFOUND) + continue; + else if (error < 0) + break; + } + + if (!iterator__include_trees(iter)) + continue; } + filesystem_iterator_set_current(iter, entry); break; } + if (out) + *out = (error == 0) ? &iter->entry : NULL; + + return error; +} + +static int filesystem_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator_frame *frame; + filesystem_iterator_entry *prev_entry; + int error; + + if (out) + *out = NULL; + + if ((frame = filesystem_iterator_current_frame(iter)) == NULL) + return GIT_ITEROVER; + + /* get the last seen entry */ + prev_entry = filesystem_iterator_current_entry(frame); + + /* it's legal to call advance_into when auto-expand is on. in this case, + * we will have pushed a new (empty) frame on to the stack for this + * new directory. since it's empty, its current_entry should be null. + */ + assert(iterator__do_autoexpand(i) ^ (prev_entry != NULL)); + + if (prev_entry) { + if (prev_entry->st.st_mode != GIT_FILEMODE_COMMIT && + !S_ISDIR(prev_entry->st.st_mode)) + return 0; + + if ((error = filesystem_iterator_frame_push(iter, prev_entry)) < 0) + return error; + } + + /* we've advanced into the directory in question, let advance + * find the first entry + */ + return filesystem_iterator_advance(out, i); +} + +int git_iterator_current_workdir_path(git_buf **out, git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + const git_index_entry *entry; + + if (i->type != GIT_ITERATOR_TYPE_FS && + i->type != GIT_ITERATOR_TYPE_WORKDIR) { + *out = NULL; + return 0; + } + + git_buf_truncate(&iter->current_path, iter->root_len); + + if (git_iterator_current(&entry, i) < 0 || + git_buf_puts(&iter->current_path, entry->path) < 0) + return -1; + + *out = &iter->current_path; return 0; } -static int fs_iterator__initialize( - git_iterator **out, fs_iterator *fi, const char *root) +GIT_INLINE(git_dir_flag) entry_dir_flag(git_index_entry *entry) +{ +#if defined(GIT_WIN32) && !defined(__MINGW32__) + return (entry && entry->mode) ? + (S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE) : + GIT_DIR_FLAG_UNKNOWN; +#else + GIT_UNUSED(entry); + return GIT_DIR_FLAG_UNKNOWN; +#endif +} + +static void filesystem_iterator_update_ignored(filesystem_iterator *iter) +{ + filesystem_iterator_frame *frame; + git_dir_flag dir_flag = entry_dir_flag(&iter->entry); + + if (git_ignore__lookup(&iter->current_is_ignored, + &iter->ignores, iter->entry.path, dir_flag) < 0) { + giterr_clear(); + iter->current_is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (iter->current_is_ignored <= GIT_IGNORE_NOTFOUND) { + frame = filesystem_iterator_current_frame(iter); + iter->current_is_ignored = frame->is_ignored; + } +} + +GIT_INLINE(bool) filesystem_iterator_current_is_ignored( + filesystem_iterator *iter) +{ + if (iter->current_is_ignored == GIT_IGNORE_UNCHECKED) + filesystem_iterator_update_ignored(iter); + + return (iter->current_is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_is_ignored(git_iterator *i) +{ + if (i->type != GIT_ITERATOR_TYPE_WORKDIR) + return false; + + return filesystem_iterator_current_is_ignored((filesystem_iterator *)i); +} + +bool git_iterator_current_tree_is_ignored(git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator_frame *frame; + + if (i->type != GIT_ITERATOR_TYPE_WORKDIR) + return false; + + frame = filesystem_iterator_current_frame(iter); + return (frame->is_ignored == GIT_IGNORE_TRUE); +} + +static int filesystem_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + filesystem_iterator_frame *current_frame; + filesystem_iterator_entry *current_entry; + const git_index_entry *entry = NULL; + const char *base; + int error = 0; + + *out = NULL; + *status = GIT_ITERATOR_STATUS_NORMAL; + + assert(iterator__has_been_accessed(i)); + + current_frame = filesystem_iterator_current_frame(iter); + assert(current_frame); + current_entry = filesystem_iterator_current_entry(current_frame); + assert(current_entry); + + if ((error = git_iterator_current(&entry, i)) < 0) + return error; + + if (!S_ISDIR(entry->mode)) { + if (filesystem_iterator_current_is_ignored(iter)) + *status = GIT_ITERATOR_STATUS_IGNORED; + + return filesystem_iterator_advance(out, i); + } + + git_buf_clear(&iter->tmp_buf); + if ((error = git_buf_puts(&iter->tmp_buf, entry->path)) < 0) + return error; + + base = iter->tmp_buf.ptr; + + /* scan inside the directory looking for files. if we find nothing, + * we will remain EMPTY. if we find any ignored item, upgrade EMPTY to + * IGNORED. if we find a real actual item, upgrade all the way to NORMAL + * and then stop. + * + * however, if we're here looking for a pathlist item (but are not + * actually in the pathlist ourselves) then start at FILTERED instead of + * EMPTY. callers then know that this path was not something they asked + * about. + */ + *status = current_entry->match == ITERATOR_PATHLIST_IS_PARENT ? + GIT_ITERATOR_STATUS_FILTERED : GIT_ITERATOR_STATUS_EMPTY; + + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if (filesystem_iterator_current_is_ignored(iter)) { + /* if we found an explicitly ignored item, then update from + * EMPTY to IGNORED + */ + *status = GIT_ITERATOR_STATUS_IGNORED; + } else if (S_ISDIR(entry->mode)) { + error = filesystem_iterator_advance_into(&entry, i); + + if (!error) + continue; + + /* this directory disappeared, ignore it */ + else if (error == GIT_ENOTFOUND) + error = 0; + + /* a real error occurred */ + else + break; + } else { + /* we found a non-ignored item, treat parent as untracked */ + *status = GIT_ITERATOR_STATUS_NORMAL; + break; + } + + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + /* wrap up scan back to base directory */ + while (entry && !iter->base.prefixcomp(entry->path, base)) { + if ((error = git_iterator_advance(&entry, i)) < 0) + break; + } + + if (!error) + *out = entry; + + return error; +} + +static void filesystem_iterator_clear(filesystem_iterator *iter) +{ + while (iter->frames.size) + filesystem_iterator_frame_pop(iter); + + git_array_clear(iter->frames); + git_ignore__free(&iter->ignores); + + git_buf_free(&iter->tmp_buf); + + iterator_clear(&iter->base); +} + +static int filesystem_iterator_init(filesystem_iterator *iter) { int error; - if (git_buf_sets(&fi->path, root) < 0 || git_path_to_dir(&fi->path) < 0) { - git__free(fi); - return -1; + if (iterator__honor_ignores(&iter->base) && + (error = git_ignore__for_path(iter->base.repo, + ".gitignore", &iter->ignores)) < 0) + return error; + + if ((error = filesystem_iterator_frame_push(iter, NULL)) < 0) + return error; + + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + + return 0; +} + +static int filesystem_iterator_reset(git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + + filesystem_iterator_clear(iter); + return filesystem_iterator_init(iter); +} + +static void filesystem_iterator_free(git_iterator *i) +{ + filesystem_iterator *iter = (filesystem_iterator *)i; + git__free(iter->root); + git_buf_free(&iter->current_path); + git_tree_free(iter->tree); + if (iter->index) + git_index_snapshot_release(&iter->index_snapshot, iter->index); + filesystem_iterator_clear(iter); +} + +static int iterator_for_filesystem( + git_iterator **out, + git_repository *repo, + const char *root, + git_index *index, + git_tree *tree, + git_iterator_type_t type, + git_iterator_options *options) +{ + filesystem_iterator *iter; + size_t root_len; + int error; + + static git_iterator_callbacks callbacks = { + filesystem_iterator_current, + filesystem_iterator_advance, + filesystem_iterator_advance_into, + filesystem_iterator_advance_over, + filesystem_iterator_reset, + filesystem_iterator_free + }; + + *out = NULL; + + if (root == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(filesystem_iterator)); + GITERR_CHECK_ALLOC(iter); + + iter->base.type = type; + iter->base.cb = &callbacks; + + root_len = strlen(root); + + iter->root = git__malloc(root_len+2); + GITERR_CHECK_ALLOC(iter->root); + + memcpy(iter->root, root, root_len); + + if (root_len == 0 || root[root_len-1] != '/') { + iter->root[root_len] = '/'; + root_len++; } - fi->root_len = fi->path.size; - fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD; + iter->root[root_len] = '\0'; + iter->root_len = root_len; - fi->dirload_flags = - (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) | - (iterator__flag(fi, PRECOMPOSE_UNICODE) ? - GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0); + if ((error = git_buf_puts(&iter->current_path, iter->root)) < 0) + goto on_error; - if ((error = fs_iterator__expand_dir(fi)) < 0) { - if (error == GIT_ENOTFOUND || error == GIT_ITEROVER) { - giterr_clear(); - error = 0; - } else { - git_iterator_free((git_iterator *)fi); - fi = NULL; - } - } + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0) + goto on_error; - *out = (git_iterator *)fi; + if (tree && (error = git_tree_dup(&iter->tree, tree)) < 0) + goto on_error; + + if (index && + (error = git_index_snapshot_new(&iter->index_snapshot, index)) < 0) + goto on_error; + + iter->index = index; + iter->dirload_flags = + (iterator__ignore_case(&iter->base) ? GIT_PATH_DIR_IGNORE_CASE : 0) | + (iterator__flag(&iter->base, PRECOMPOSE_UNICODE) ? + GIT_PATH_DIR_PRECOMPOSE_UNICODE : 0); + + if ((error = filesystem_iterator_init(iter)) < 0) + goto on_error; + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); return error; } @@ -1629,187 +1886,8 @@ int git_iterator_for_filesystem( const char *root, git_iterator_options *options) { - fs_iterator *fi = git__calloc(1, sizeof(fs_iterator)); - GITERR_CHECK_ALLOC(fi); - - ITERATOR_BASE_INIT(fi, fs, FS, NULL); - - if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) - fi->base.flags |= GIT_ITERATOR_IGNORE_CASE; - - return fs_iterator__initialize(out, fi, root); -} - - -typedef struct { - fs_iterator fi; - git_ignores ignores; - int is_ignored; - - /* - * We may have a tree or the index+snapshot to compare against - * when checking for submodules. - */ - git_tree *tree; - git_index *index; - git_vector index_snapshot; - git_vector_cmp entry_srch; - -} workdir_iterator; - -GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path) -{ - size_t len; - - if (!path || (len = path->size) < 4) - return false; - - if (path->ptr[len - 1] == '/') - len--; - - if (git__tolower(path->ptr[len - 1]) != 't' || - git__tolower(path->ptr[len - 2]) != 'i' || - git__tolower(path->ptr[len - 3]) != 'g' || - git__tolower(path->ptr[len - 4]) != '.') - return false; - - return (len == 4 || path->ptr[len - 5] == '/'); -} - -/** - * Figure out if an entry is a submodule. - * - * We consider it a submodule if the path is listed as a submodule in - * either the tree or the index. - */ -static int is_submodule(workdir_iterator *wi, fs_iterator_path_with_stat *ie) -{ - int error, is_submodule = 0; - - if (wi->tree) { - git_tree_entry *e; - - /* remove the trailing slash for finding */ - ie->path[ie->path_len-1] = '\0'; - error = git_tree_entry_bypath(&e, wi->tree, ie->path); - ie->path[ie->path_len-1] = '/'; - if (error < 0 && error != GIT_ENOTFOUND) - return 0; - if (!error) { - is_submodule = e->attr == GIT_FILEMODE_COMMIT; - git_tree_entry_free(e); - } - } - - if (!is_submodule && wi->index) { - git_index_entry *e; - size_t pos; - - error = git_index_snapshot_find(&pos, &wi->index_snapshot, wi->entry_srch, ie->path, ie->path_len-1, 0); - if (error < 0 && error != GIT_ENOTFOUND) - return 0; - - if (!error) { - e = git_vector_get(&wi->index_snapshot, pos); - - is_submodule = e->mode == GIT_FILEMODE_COMMIT; - } - } - - return is_submodule; -} - -GIT_INLINE(git_dir_flag) git_entry__dir_flag(git_index_entry *entry) { -#if defined(GIT_WIN32) && !defined(__MINGW32__) - return (entry && entry->mode) - ? S_ISDIR(entry->mode) ? GIT_DIR_FLAG_TRUE : GIT_DIR_FLAG_FALSE - : GIT_DIR_FLAG_UNKNOWN; -#else - GIT_UNUSED(entry); - return GIT_DIR_FLAG_UNKNOWN; -#endif -} - -static int workdir_iterator__enter_dir(fs_iterator *fi) -{ - workdir_iterator *wi = (workdir_iterator *)fi; - fs_iterator_frame *ff = fi->stack; - size_t pos; - fs_iterator_path_with_stat *entry; - bool found_submodules = false; - - git_dir_flag dir_flag = git_entry__dir_flag(&fi->entry); - - /* check if this directory is ignored */ - if (git_ignore__lookup(&ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len, dir_flag) < 0) { - giterr_clear(); - ff->is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* if this is not the top level directory... */ - if (ff->next != NULL) { - ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/'); - - /* inherit ignored from parent if no rule specified */ - if (ff->is_ignored <= GIT_IGNORE_NOTFOUND) - ff->is_ignored = ff->next->is_ignored; - - /* push new ignores for files in this directory */ - (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]); - } - - /* convert submodules to GITLINK and remove trailing slashes */ - git_vector_foreach(&ff->entries, pos, entry) { - if (!S_ISDIR(entry->st.st_mode) || !strcmp(GIT_DIR, entry->path)) - continue; - - if (is_submodule(wi, entry)) { - entry->st.st_mode = GIT_FILEMODE_COMMIT; - entry->path_len--; - entry->path[entry->path_len] = '\0'; - found_submodules = true; - } - } - - /* if we renamed submodules, re-sort and re-seek to start */ - if (found_submodules) { - git_vector_set_sorted(&ff->entries, 0); - git_vector_sort(&ff->entries); - fs_iterator__seek_frame_start(fi, ff); - } - - return 0; -} - -static int workdir_iterator__leave_dir(fs_iterator *fi) -{ - workdir_iterator *wi = (workdir_iterator *)fi; - git_ignore__pop_dir(&wi->ignores); - return 0; -} - -static int workdir_iterator__update_entry(fs_iterator *fi) -{ - workdir_iterator *wi = (workdir_iterator *)fi; - - /* skip over .git entries */ - if (workdir_path_is_dotgit(&fi->path)) - return GIT_ENOTFOUND; - - /* reset is_ignored since we haven't checked yet */ - wi->is_ignored = GIT_IGNORE_UNCHECKED; - - return 0; -} - -static void workdir_iterator__free(git_iterator *self) -{ - workdir_iterator *wi = (workdir_iterator *)self; - if (wi->index) - git_index_snapshot_release(&wi->index_snapshot, wi->index); - git_tree_free(wi->tree); - fs_iterator__free(self); - git_ignore__free(&wi->ignores); + return iterator_for_filesystem(out, + NULL, root, NULL, NULL, GIT_ITERATOR_TYPE_FS, options); } int git_iterator_for_workdir_ext( @@ -1818,54 +1896,310 @@ int git_iterator_for_workdir_ext( const char *repo_workdir, git_index *index, git_tree *tree, - git_iterator_options *options) + git_iterator_options *given_opts) { - int error, precompose = 0; - workdir_iterator *wi; + git_iterator_options options = GIT_ITERATOR_OPTIONS_INIT; if (!repo_workdir) { if (git_repository__ensure_not_bare(repo, "scan working directory") < 0) return GIT_EBAREREPO; + repo_workdir = git_repository_workdir(repo); } - /* initialize as an fs iterator then do overrides */ - wi = git__calloc(1, sizeof(workdir_iterator)); - GITERR_CHECK_ALLOC(wi); - ITERATOR_BASE_INIT((&wi->fi), fs, FS, repo); + /* upgrade to a workdir iterator, adding necessary internal flags */ + if (given_opts) + memcpy(&options, given_opts, sizeof(git_iterator_options)); - wi->fi.base.type = GIT_ITERATOR_TYPE_WORKDIR; - wi->fi.cb.free = workdir_iterator__free; - wi->fi.enter_dir_cb = workdir_iterator__enter_dir; - wi->fi.leave_dir_cb = workdir_iterator__leave_dir; - wi->fi.update_entry_cb = workdir_iterator__update_entry; + options.flags |= GIT_ITERATOR_HONOR_IGNORES | + GIT_ITERATOR_IGNORE_DOT_GIT; - if ((error = iterator__update_ignore_case((git_iterator *)wi, options ? options->flags : 0)) < 0 || - (error = git_ignore__for_path(repo, ".gitignore", &wi->ignores)) < 0) - { - git_iterator_free((git_iterator *)wi); - return error; + return iterator_for_filesystem(out, + repo, repo_workdir, index, tree, GIT_ITERATOR_TYPE_WORKDIR, &options); +} + + +/* Index iterator */ + + +typedef struct { + git_iterator base; + git_vector entries; + size_t next_idx; + + /* the pseudotree entry */ + git_index_entry tree_entry; + git_buf tree_buf; + bool skip_tree; + + const git_index_entry *entry; +} index_iterator; + +static int index_iterator_current( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + if (!iterator__has_been_accessed(i)) + return iter->base.cb->advance(out, i); + + if (iter->entry == NULL) { + *out = NULL; + return GIT_ITEROVER; } - if (tree && (error = git_object_dup((git_object **)&wi->tree, (git_object *)tree)) < 0) - return error; + *out = iter->entry; + return 0; +} - wi->index = index; - if (index && (error = git_index_snapshot_new(&wi->index_snapshot, index)) < 0) { - git_iterator_free((git_iterator *)wi); - return error; +static bool index_iterator_create_pseudotree( + const git_index_entry **out, + index_iterator *iter, + const char *path) +{ + const char *prev_path, *relative_path, *dirsep; + size_t common_len; + + prev_path = iter->entry ? iter->entry->path : ""; + + /* determine if the new path is in a different directory from the old */ + common_len = git_path_common_dirlen(prev_path, path); + relative_path = path + common_len; + + if ((dirsep = strchr(relative_path, '/')) == NULL) + return false; + + git_buf_clear(&iter->tree_buf); + git_buf_put(&iter->tree_buf, path, (dirsep - path) + 1); + + iter->tree_entry.mode = GIT_FILEMODE_TREE; + iter->tree_entry.path = iter->tree_buf.ptr; + + *out = &iter->tree_entry; + return true; +} + +static int index_iterator_skip_pseudotree(index_iterator *iter) +{ + assert(iterator__has_been_accessed(&iter->base)); + assert(S_ISDIR(iter->entry->mode)); + + while (true) { + const git_index_entry *next_entry = NULL; + + if (++iter->next_idx >= iter->entries.length) + return GIT_ITEROVER; + + next_entry = iter->entries.contents[iter->next_idx]; + + if (iter->base.strncomp(iter->tree_buf.ptr, next_entry->path, + iter->tree_buf.size) != 0) + break; } - wi->entry_srch = iterator__ignore_case(wi) ? - git_index_entry_isrch : git_index_entry_srch; + + iter->skip_tree = false; + return 0; +} + +static int index_iterator_advance( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + const git_index_entry *entry = NULL; + bool is_submodule; + int error = 0; + + iter->base.flags |= GIT_ITERATOR_FIRST_ACCESS; + + while (true) { + if (iter->next_idx >= iter->entries.length) { + error = GIT_ITEROVER; + break; + } + + /* we were not asked to expand this pseudotree. advance over it. */ + if (iter->skip_tree) { + index_iterator_skip_pseudotree(iter); + continue; + } + + entry = iter->entries.contents[iter->next_idx]; + is_submodule = S_ISGITLINK(entry->mode); + + if (!iterator_has_started(&iter->base, entry->path, is_submodule)) { + iter->next_idx++; + continue; + } + + if (iterator_has_ended(&iter->base, entry->path)) { + error = GIT_ITEROVER; + break; + } + + /* if we have a list of paths we're interested in, examine it */ + if (!iterator_pathlist_next_is(&iter->base, entry->path)) { + iter->next_idx++; + continue; + } + + /* if this is a conflict, skip it unless we're including conflicts */ + if (git_index_entry_is_conflict(entry) && + !iterator__include_conflicts(&iter->base)) { + iter->next_idx++; + continue; + } + + /* we've found what will be our next _file_ entry. but if we are + * returning trees entries, we may need to return a pseudotree + * entry that will contain this. don't advance over this entry, + * though, we still need to return it on the next `advance`. + */ + if (iterator__include_trees(&iter->base) && + index_iterator_create_pseudotree(&entry, iter, entry->path)) { + + /* Note whether this pseudo tree should be expanded or not */ + iter->skip_tree = iterator__dont_autoexpand(&iter->base); + break; + } + + iter->next_idx++; + break; + } + + iter->entry = (error == 0) ? entry : NULL; + + if (out) + *out = iter->entry; + + return error; +} + +static int index_iterator_advance_into( + const git_index_entry **out, git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + if (! S_ISDIR(iter->tree_entry.mode)) { + if (out) + *out = NULL; + + return 0; + } + + iter->skip_tree = false; + return index_iterator_advance(out, i); +} + +static int index_iterator_advance_over( + const git_index_entry **out, + git_iterator_status_t *status, + git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + const git_index_entry *entry; + int error; + + if ((error = index_iterator_current(&entry, i)) < 0) + return error; + + if (S_ISDIR(entry->mode)) + index_iterator_skip_pseudotree(iter); + + *status = GIT_ITERATOR_STATUS_NORMAL; + return index_iterator_advance(out, i); +} + +static void index_iterator_clear(index_iterator *iter) +{ + iterator_clear(&iter->base); +} + +static int index_iterator_init(index_iterator *iter) +{ + iter->base.flags &= ~GIT_ITERATOR_FIRST_ACCESS; + iter->next_idx = 0; + iter->skip_tree = false; + return 0; +} + +static int index_iterator_reset(git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + index_iterator_clear(iter); + return index_iterator_init(iter); +} + +static void index_iterator_free(git_iterator *i) +{ + index_iterator *iter = (index_iterator *)i; + + git_index_snapshot_release(&iter->entries, iter->base.index); + git_buf_free(&iter->tree_buf); +} + +int git_iterator_for_index( + git_iterator **out, + git_repository *repo, + git_index *index, + git_iterator_options *options) +{ + index_iterator *iter; + int error; + + static git_iterator_callbacks callbacks = { + index_iterator_current, + index_iterator_advance, + index_iterator_advance_into, + index_iterator_advance_over, + index_iterator_reset, + index_iterator_free + }; + + *out = NULL; + + if (index == NULL) + return git_iterator_for_nothing(out, options); + + iter = git__calloc(1, sizeof(index_iterator)); + GITERR_CHECK_ALLOC(iter); + + iter->base.type = GIT_ITERATOR_TYPE_INDEX; + iter->base.cb = &callbacks; + + if ((error = iterator_init_common(&iter->base, repo, index, options)) < 0 || + (error = git_index_snapshot_new(&iter->entries, index)) < 0 || + (error = index_iterator_init(iter)) < 0) + goto on_error; + + git_vector_set_cmp(&iter->entries, iterator__ignore_case(&iter->base) ? + git_index_entry_icmp : git_index_entry_cmp); + git_vector_sort(&iter->entries); + + *out = &iter->base; + return 0; + +on_error: + git_iterator_free(&iter->base); + return error; +} - /* try to look up precompose and set flag if appropriate */ - if (git_repository__cvar(&precompose, repo, GIT_CVAR_PRECOMPOSE) < 0) - giterr_clear(); - else if (precompose) - wi->fi.base.flags |= GIT_ITERATOR_PRECOMPOSE_UNICODE; +/* Iterator API */ - return fs_iterator__initialize(out, &wi->fi, repo_workdir); +int git_iterator_reset_range( + git_iterator *i, const char *start, const char *end) +{ + if (iterator_reset_range(i, start, end) < 0) + return -1; + + return i->cb->reset(i); +} + +void git_iterator_set_ignore_case(git_iterator *i, bool ignore_case) +{ + assert(!iterator__has_been_accessed(i)); + iterator_set_ignore_case(i, ignore_case); } void git_iterator_free(git_iterator *iter) @@ -1884,235 +2218,6 @@ void git_iterator_free(git_iterator *iter) git__free(iter); } -int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case) -{ - bool desire_ignore_case = (ignore_case != 0); - - if (iterator__ignore_case(iter) == desire_ignore_case) - return 0; - - if (iter->type == GIT_ITERATOR_TYPE_EMPTY) { - if (desire_ignore_case) - iter->flags |= GIT_ITERATOR_IGNORE_CASE; - else - iter->flags &= ~GIT_ITERATOR_IGNORE_CASE; - } else { - giterr_set(GITERR_INVALID, - "Cannot currently set ignore case on non-empty iterators"); - return -1; - } - - return 0; -} - -git_index *git_iterator_get_index(git_iterator *iter) -{ - if (iter->type == GIT_ITERATOR_TYPE_INDEX) - return ((index_iterator *)iter)->index; - return NULL; -} - -int git_iterator_current_tree_entry( - const git_tree_entry **tree_entry, git_iterator *iter) -{ - if (iter->type != GIT_ITERATOR_TYPE_TREE) - *tree_entry = NULL; - else { - tree_iterator_frame *tf = ((tree_iterator *)iter)->head; - *tree_entry = (tf->current < tf->n_entries) ? - tf->entries[tf->current]->te : NULL; - } - - return 0; -} - -int git_iterator_current_parent_tree( - const git_tree **tree_ptr, - git_iterator *iter, - const char *parent_path) -{ - tree_iterator *ti = (tree_iterator *)iter; - tree_iterator_frame *tf; - const char *scan = parent_path; - const git_tree_entry *te; - - *tree_ptr = NULL; - - if (iter->type != GIT_ITERATOR_TYPE_TREE) - return 0; - - for (tf = ti->root; *scan; ) { - if (!(tf = tf->down) || - tf->current >= tf->n_entries || - !(te = tf->entries[tf->current]->te) || - ti->base.strncomp(scan, te->filename, te->filename_len) != 0) - return 0; - - scan += te->filename_len; - if (*scan == '/') - scan++; - } - - *tree_ptr = tf->entries[tf->current]->tree; - return 0; -} - -static void workdir_iterator_update_is_ignored(workdir_iterator *wi) -{ - git_dir_flag dir_flag = git_entry__dir_flag(&wi->fi.entry); - - if (git_ignore__lookup(&wi->is_ignored, &wi->ignores, wi->fi.entry.path, dir_flag) < 0) { - giterr_clear(); - wi->is_ignored = GIT_IGNORE_NOTFOUND; - } - - /* use ignore from containing frame stack */ - if (wi->is_ignored <= GIT_IGNORE_NOTFOUND) - wi->is_ignored = wi->fi.stack->is_ignored; -} - -bool git_iterator_current_is_ignored(git_iterator *iter) -{ - workdir_iterator *wi = (workdir_iterator *)iter; - - if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) - return false; - - if (wi->is_ignored != GIT_IGNORE_UNCHECKED) - return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); - - workdir_iterator_update_is_ignored(wi); - - return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); -} - -bool git_iterator_current_tree_is_ignored(git_iterator *iter) -{ - workdir_iterator *wi = (workdir_iterator *)iter; - - if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) - return false; - - return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE); -} - -int git_iterator_cmp(git_iterator *iter, const char *path_prefix) -{ - const git_index_entry *entry; - - /* a "done" iterator is after every prefix */ - if (git_iterator_current(&entry, iter) < 0 || entry == NULL) - return 1; - - /* a NULL prefix is after any valid iterator */ - if (!path_prefix) - return -1; - - return iter->prefixcomp(entry->path, path_prefix); -} - -int git_iterator_current_workdir_path(git_buf **path, git_iterator *iter) -{ - workdir_iterator *wi = (workdir_iterator *)iter; - - if (iter->type != GIT_ITERATOR_TYPE_WORKDIR || !wi->fi.entry.path) - *path = NULL; - else - *path = &wi->fi.path; - - return 0; -} - -int git_iterator_index(git_index **out, git_iterator *iter) -{ - workdir_iterator *wi = (workdir_iterator *)iter; - - if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) - *out = NULL; - - *out = wi->index; - - return 0; -} - -int git_iterator_advance_over_with_status( - const git_index_entry **entryptr, - git_iterator_status_t *status, - git_iterator *iter) -{ - int error = 0; - workdir_iterator *wi = (workdir_iterator *)iter; - char *base = NULL; - const git_index_entry *entry; - - *status = GIT_ITERATOR_STATUS_NORMAL; - - if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) - return git_iterator_advance(entryptr, iter); - if ((error = git_iterator_current(&entry, iter)) < 0) - return error; - - if (!S_ISDIR(entry->mode)) { - workdir_iterator_update_is_ignored(wi); - if (wi->is_ignored == GIT_IGNORE_TRUE) - *status = GIT_ITERATOR_STATUS_IGNORED; - return git_iterator_advance(entryptr, iter); - } - - *status = GIT_ITERATOR_STATUS_EMPTY; - - base = git__strdup(entry->path); - GITERR_CHECK_ALLOC(base); - - /* scan inside directory looking for a non-ignored item */ - while (entry && !iter->prefixcomp(entry->path, base)) { - workdir_iterator_update_is_ignored(wi); - - /* if we found an explicitly ignored item, then update from - * EMPTY to IGNORED - */ - if (wi->is_ignored == GIT_IGNORE_TRUE) - *status = GIT_ITERATOR_STATUS_IGNORED; - else if (S_ISDIR(entry->mode)) { - error = git_iterator_advance_into(&entry, iter); - - if (!error) - continue; - - else if (error == GIT_ENOTFOUND) { - /* we entered this directory only hoping to find child matches to - * our pathlist (eg, this is `foo` and we had a pathlist entry for - * `foo/bar`). it should not be ignored, it should be excluded. - */ - if (wi->fi.pathlist_match == ITERATOR_PATHLIST_MATCH_CHILD) - *status = GIT_ITERATOR_STATUS_FILTERED; - else - wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ - - error = 0; - } else - break; /* real error, stop here */ - } else { - /* we found a non-ignored item, treat parent as untracked */ - *status = GIT_ITERATOR_STATUS_NORMAL; - break; - } - - if ((error = git_iterator_advance(&entry, iter)) < 0) - break; - } - - /* wrap up scan back to base directory */ - while (entry && !iter->prefixcomp(entry->path, base)) - if ((error = git_iterator_advance(&entry, iter)) < 0) - break; - - *entryptr = entry; - git__free(base); - - return error; -} - int git_iterator_walk( git_iterator **iterators, size_t cnt, diff --git a/src/iterator.h b/src/iterator.h index ac17d2970..0b239a5bd 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -34,10 +34,19 @@ typedef enum { GIT_ITERATOR_DONT_AUTOEXPAND = (1u << 3), /** convert precomposed unicode to decomposed unicode */ GIT_ITERATOR_PRECOMPOSE_UNICODE = (1u << 4), + /** never convert precomposed unicode to decomposed unicode */ + GIT_ITERATOR_DONT_PRECOMPOSE_UNICODE = (1u << 5), /** include conflicts */ - GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5), + GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 6), } git_iterator_flag_t; +typedef enum { + GIT_ITERATOR_STATUS_NORMAL = 0, + GIT_ITERATOR_STATUS_IGNORED = 1, + GIT_ITERATOR_STATUS_EMPTY = 2, + GIT_ITERATOR_STATUS_FILTERED = 3 +} git_iterator_status_t; + typedef struct { const char *start; const char *end; @@ -57,23 +66,33 @@ typedef struct { int (*current)(const git_index_entry **, git_iterator *); int (*advance)(const git_index_entry **, git_iterator *); int (*advance_into)(const git_index_entry **, git_iterator *); - int (*seek)(git_iterator *, const char *prefix); - int (*reset)(git_iterator *, const char *start, const char *end); - int (*at_end)(git_iterator *); + int (*advance_over)( + const git_index_entry **, git_iterator_status_t *, git_iterator *); + int (*reset)(git_iterator *); void (*free)(git_iterator *); } git_iterator_callbacks; struct git_iterator { git_iterator_type_t type; git_iterator_callbacks *cb; + git_repository *repo; + git_index *index; + char *start; + size_t start_len; + char *end; + size_t end_len; + + bool started; + bool ended; git_vector pathlist; size_t pathlist_walk_idx; int (*strcomp)(const char *a, const char *b); int (*strncomp)(const char *a, const char *b, size_t n); int (*prefixcomp)(const char *str, const char *prefix); + int (*entry_srch)(const void *key, const void *array_member); size_t stat_calls; unsigned int flags; }; @@ -181,54 +200,38 @@ GIT_INLINE(int) git_iterator_advance_into( return iter->cb->advance_into(entry, iter); } -/** - * Advance into a tree or skip over it if it is empty. +/* Advance over a directory and check if it contains no files or just + * ignored files. * - * Because `git_iterator_advance_into` may return GIT_ENOTFOUND if the - * directory is empty (only with filesystem and working directory - * iterators) and a common response is to just call `git_iterator_advance` - * when that happens, this bundles the two into a single simple call. + * In a tree or the index, all directories will contain files, but in the + * working directory it is possible to have an empty directory tree or a + * tree that only contains ignored files. Many Git operations treat these + * cases specially. This advances over a directory (presumably an + * untracked directory) but checks during the scan if there are any files + * and any non-ignored files. */ -GIT_INLINE(int) git_iterator_advance_into_or_over( - const git_index_entry **entry, git_iterator *iter) +GIT_INLINE(int) git_iterator_advance_over( + const git_index_entry **entry, + git_iterator_status_t *status, + git_iterator *iter) { - int error = iter->cb->advance_into(entry, iter); - if (error == GIT_ENOTFOUND) { - giterr_clear(); - error = iter->cb->advance(entry, iter); - } - return error; -} - -/* Seek is currently unimplemented */ -GIT_INLINE(int) git_iterator_seek( - git_iterator *iter, const char *prefix) -{ - return iter->cb->seek(iter, prefix); + return iter->cb->advance_over(entry, status, iter); } /** * Go back to the start of the iteration. - * - * This resets the iterator to the start of the iteration. It also allows - * you to reset the `start` and `end` pathname boundaries of the iteration - * when doing so. */ -GIT_INLINE(int) git_iterator_reset( - git_iterator *iter, const char *start, const char *end) +GIT_INLINE(int) git_iterator_reset(git_iterator *iter) { - return iter->cb->reset(iter, start, end); + return iter->cb->reset(iter); } /** - * Check if the iterator is at the end - * - * @return 0 if not at end, >0 if at end + * Go back to the start of the iteration after updating the `start` and + * `end` pathname boundaries of the iteration. */ -GIT_INLINE(int) git_iterator_at_end(git_iterator *iter) -{ - return iter->cb->at_end(iter); -} +extern int git_iterator_reset_range( + git_iterator *iter, const char *start, const char *end); GIT_INLINE(git_iterator_type_t) git_iterator_type(git_iterator *iter) { @@ -240,6 +243,11 @@ GIT_INLINE(git_repository *) git_iterator_owner(git_iterator *iter) return iter->repo; } +GIT_INLINE(git_index *) git_iterator_index(git_iterator *iter) +{ + return iter->index; +} + GIT_INLINE(git_iterator_flag_t) git_iterator_flags(git_iterator *iter) { return iter->flags; @@ -250,21 +258,19 @@ GIT_INLINE(bool) git_iterator_ignore_case(git_iterator *iter) return ((iter->flags & GIT_ITERATOR_IGNORE_CASE) != 0); } -extern int git_iterator_set_ignore_case(git_iterator *iter, bool ignore_case); +extern void git_iterator_set_ignore_case( + git_iterator *iter, bool ignore_case); extern int git_iterator_current_tree_entry( const git_tree_entry **entry_out, git_iterator *iter); extern int git_iterator_current_parent_tree( - const git_tree **tree_out, git_iterator *iter, const char *parent_path); + const git_tree **tree_out, git_iterator *iter, size_t depth); extern bool git_iterator_current_is_ignored(git_iterator *iter); extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); -extern int git_iterator_cmp( - git_iterator *iter, const char *path_prefix); - /** * Get full path of the current item from a workdir iterator. This will * return NULL for a non-workdir iterator. The git_buf is still owned by @@ -273,35 +279,12 @@ extern int git_iterator_cmp( extern int git_iterator_current_workdir_path( git_buf **path, git_iterator *iter); -/* Return index pointer if index iterator, else NULL */ -extern git_index *git_iterator_get_index(git_iterator *iter); - -typedef enum { - GIT_ITERATOR_STATUS_NORMAL = 0, - GIT_ITERATOR_STATUS_IGNORED = 1, - GIT_ITERATOR_STATUS_EMPTY = 2, - GIT_ITERATOR_STATUS_FILTERED = 3 -} git_iterator_status_t; - -/* Advance over a directory and check if it contains no files or just - * ignored files. - * - * In a tree or the index, all directories will contain files, but in the - * working directory it is possible to have an empty directory tree or a - * tree that only contains ignored files. Many Git operations treat these - * cases specially. This advances over a directory (presumably an - * untracked directory) but checks during the scan if there are any files - * and any non-ignored files. - */ -extern int git_iterator_advance_over_with_status( - const git_index_entry **entry, git_iterator_status_t *status, git_iterator *iter); - /** * Retrieve the index stored in the iterator. * - * Only implemented for the workdir iterator + * Only implemented for the workdir and index iterators. */ -extern int git_iterator_index(git_index **out, git_iterator *iter); +extern git_index *git_iterator_index(git_iterator *iter); typedef int (*git_iterator_walk_cb)( const git_index_entry **entries, diff --git a/src/merge.c b/src/merge.c index 174cbfb58..1142917bd 100644 --- a/src/merge.c +++ b/src/merge.c @@ -18,6 +18,8 @@ #include "iterator.h" #include "refs.h" #include "diff.h" +#include "diff_generate.h" +#include "diff_tform.h" #include "checkout.h" #include "tree.h" #include "blob.h" @@ -29,6 +31,7 @@ #include "annotated_commit.h" #include "commit.h" #include "oidarray.h" +#include "merge_driver.h" #include "git2/types.h" #include "git2/repository.h" @@ -50,18 +53,6 @@ #define GIT_MERGE_INDEX_ENTRY_ISFILE(X) S_ISREG((X).mode) -/** Internal merge flags. */ -enum { - /** The merge is for a virtual base in a recursive merge. */ - GIT_MERGE__VIRTUAL_BASE = (1 << 31), -}; - -enum { - /** Accept the conflict file, staging it as the merge result. */ - GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, -}; - - typedef enum { TREE_IDX_ANCESTOR = 0, TREE_IDX_OURS = 1, @@ -273,7 +264,7 @@ int git_merge_base(git_oid *out, git_repository *repo, const git_oid *one, const int git_merge_bases(git_oidarray *out, git_repository *repo, const git_oid *one, const git_oid *two) { int error; - git_revwalk *walk; + git_revwalk *walk; git_commit_list *result, *list; git_array_oid_t array; @@ -600,7 +591,7 @@ int git_repository_mergehead_foreach( } if (*buffer) { - giterr_set(GITERR_MERGE, "No EOL at line %d", line_num); + giterr_set(GITERR_MERGE, "No EOL at line %"PRIuZ, line_num); error = -1; goto cleanup; } @@ -810,76 +801,158 @@ static int merge_conflict_resolve_one_renamed( return error; } -static int merge_conflict_resolve_automerge( - int *resolved, - git_merge_diff_list *diff_list, - const git_merge_diff *conflict, - const git_merge_file_options *file_opts) +static bool merge_conflict_can_resolve_contents( + const git_merge_diff *conflict) { - const git_index_entry *ancestor = NULL, *ours = NULL, *theirs = NULL; - git_merge_file_result result = {0}; - git_index_entry *index_entry; - git_odb *odb = NULL; - git_oid automerge_oid; - int error = 0; - - assert(resolved && diff_list && conflict); - - *resolved = 0; - if (!GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) || !GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry)) - return 0; + return false; /* Reject D/F conflicts */ if (conflict->type == GIT_MERGE_DIFF_DIRECTORY_FILE) - return 0; + return false; /* Reject submodules. */ if (S_ISGITLINK(conflict->ancestor_entry.mode) || S_ISGITLINK(conflict->our_entry.mode) || S_ISGITLINK(conflict->their_entry.mode)) - return 0; + return false; /* Reject link/file conflicts. */ - if ((S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->our_entry.mode)) || - (S_ISLNK(conflict->ancestor_entry.mode) ^ S_ISLNK(conflict->their_entry.mode))) - return 0; + if ((S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->our_entry.mode)) || + (S_ISLNK(conflict->ancestor_entry.mode) ^ + S_ISLNK(conflict->their_entry.mode))) + return false; /* Reject name conflicts */ if (conflict->type == GIT_MERGE_DIFF_BOTH_RENAMED_2_TO_1 || conflict->type == GIT_MERGE_DIFF_RENAMED_ADDED) - return 0; + return false; if ((conflict->our_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && (conflict->their_status & GIT_DELTA_RENAMED) == GIT_DELTA_RENAMED && strcmp(conflict->ancestor_entry.path, conflict->their_entry.path) != 0) - return 0; + return false; - ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? - &conflict->ancestor_entry : NULL; - ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? - &conflict->our_entry : NULL; - theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? - &conflict->their_entry : NULL; + return true; +} - if ((error = git_repository_odb(&odb, diff_list->repo)) < 0 || - (error = git_merge_file_from_index(&result, diff_list->repo, ancestor, ours, theirs, file_opts)) < 0 || - (!result.automergeable && !(file_opts->flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) || - (error = git_odb_write(&automerge_oid, odb, result.ptr, result.len, GIT_OBJ_BLOB)) < 0) +static int merge_conflict_invoke_driver( + git_index_entry **out, + const char *name, + git_merge_driver *driver, + git_merge_diff_list *diff_list, + git_merge_driver_source *src) +{ + git_index_entry *result; + git_buf buf = GIT_BUF_INIT; + const char *path; + uint32_t mode; + git_odb *odb = NULL; + git_oid oid; + int error; + + *out = NULL; + + if ((error = driver->apply(driver, &path, &mode, &buf, name, src)) < 0 || + (error = git_repository_odb(&odb, src->repo)) < 0 || + (error = git_odb_write(&oid, odb, buf.ptr, buf.size, GIT_OBJ_BLOB)) < 0) goto done; - if ((index_entry = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry))) == NULL) - GITERR_CHECK_ALLOC(index_entry); + result = git_pool_mallocz(&diff_list->pool, sizeof(git_index_entry)); + GITERR_CHECK_ALLOC(result); - index_entry->path = git_pool_strdup(&diff_list->pool, result.path); - GITERR_CHECK_ALLOC(index_entry->path); + git_oid_cpy(&result->id, &oid); + result->mode = mode; + result->file_size = buf.size; - index_entry->file_size = result.len; - index_entry->mode = result.mode; - git_oid_cpy(&index_entry->id, &automerge_oid); + result->path = git_pool_strdup(&diff_list->pool, path); + GITERR_CHECK_ALLOC(result->path); - git_vector_insert(&diff_list->staged, index_entry); + *out = result; + +done: + git_buf_free(&buf); + git_odb_free(odb); + + return error; +} + +static int merge_conflict_resolve_contents( + int *resolved, + git_merge_diff_list *diff_list, + const git_merge_diff *conflict, + const git_merge_options *merge_opts, + const git_merge_file_options *file_opts) +{ + git_merge_driver_source source = {0}; + git_merge_file_result result = {0}; + git_merge_driver *driver; + git_merge_driver__builtin builtin = {{0}}; + git_index_entry *merge_result; + git_odb *odb = NULL; + const char *name; + bool fallback = false; + int error; + + assert(resolved && diff_list && conflict); + + *resolved = 0; + + if (!merge_conflict_can_resolve_contents(conflict)) + return 0; + + source.repo = diff_list->repo; + source.default_driver = merge_opts->default_driver; + source.file_opts = file_opts; + source.ancestor = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->ancestor_entry) ? + &conflict->ancestor_entry : NULL; + source.ours = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->our_entry) ? + &conflict->our_entry : NULL; + source.theirs = GIT_MERGE_INDEX_ENTRY_EXISTS(conflict->their_entry) ? + &conflict->their_entry : NULL; + + if (file_opts->favor != GIT_MERGE_FILE_FAVOR_NORMAL) { + /* if the user requested a particular type of resolution (via the + * favor flag) then let that override the gitattributes and use + * the builtin driver. + */ + name = "text"; + builtin.base.apply = git_merge_driver__builtin_apply; + builtin.favor = file_opts->favor; + + driver = &builtin.base; + } else { + /* find the merge driver for this file */ + if ((error = git_merge_driver_for_source(&name, &driver, &source)) < 0) + goto done; + + if (driver == NULL) + fallback = true; + } + + if (driver) { + error = merge_conflict_invoke_driver(&merge_result, name, driver, + diff_list, &source); + + if (error == GIT_PASSTHROUGH) + fallback = true; + } + + if (fallback) { + error = merge_conflict_invoke_driver(&merge_result, "text", + &git_merge_driver__text.base, diff_list, &source); + } + + if (error < 0) { + if (error == GIT_EMERGECONFLICT) + error = 0; + + goto done; + } + + git_vector_insert(&diff_list->staged, merge_result); git_vector_insert(&diff_list->resolved, (git_merge_diff *)conflict); *resolved = 1; @@ -895,6 +968,7 @@ static int merge_conflict_resolve( int *out, git_merge_diff_list *diff_list, const git_merge_diff *conflict, + const git_merge_options *merge_opts, const git_merge_file_options *file_opts) { int resolved = 0; @@ -902,16 +976,20 @@ static int merge_conflict_resolve( *out = 0; - if ((error = merge_conflict_resolve_trivial(&resolved, diff_list, conflict)) < 0) + if ((error = merge_conflict_resolve_trivial( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_one_removed(&resolved, diff_list, conflict)) < 0) + if (!resolved && (error = merge_conflict_resolve_one_removed( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_one_renamed(&resolved, diff_list, conflict)) < 0) + if (!resolved && (error = merge_conflict_resolve_one_renamed( + &resolved, diff_list, conflict)) < 0) goto done; - if (!resolved && (error = merge_conflict_resolve_automerge(&resolved, diff_list, conflict, file_opts)) < 0) + if (!resolved && (error = merge_conflict_resolve_contents( + &resolved, diff_list, conflict, merge_opts, file_opts)) < 0) goto done; *out = resolved; @@ -1627,6 +1705,7 @@ static int merge_normalize_opts( const git_merge_options *given) { git_config *cfg = NULL; + git_config_entry *entry = NULL; int error = 0; assert(repo && opts); @@ -1644,6 +1723,22 @@ static int merge_normalize_opts( opts->rename_threshold = GIT_MERGE_DEFAULT_RENAME_THRESHOLD; } + if (given && given->default_driver) { + opts->default_driver = git__strdup(given->default_driver); + GITERR_CHECK_ALLOC(opts->default_driver); + } else { + error = git_config_get_entry(&entry, cfg, "merge.default"); + + if (error == 0) { + opts->default_driver = git__strdup(entry->value); + GITERR_CHECK_ALLOC(opts->default_driver); + } else if (error == GIT_ENOTFOUND) { + error = 0; + } else { + goto done; + } + } + if (!opts->target_limit) { int limit = git_config__get_int_force(cfg, "merge.renamelimit", 0); @@ -1666,7 +1761,9 @@ static int merge_normalize_opts( opts->metric->payload = (void *)GIT_HASHSIG_SMART_WHITESPACE; } - return 0; +done: + git_config_entry_free(entry); + return error; } @@ -1878,7 +1975,7 @@ int git_merge__iterators( int resolved = 0; if ((error = merge_conflict_resolve( - &resolved, diff_list, conflict, &file_opts)) < 0) + &resolved, diff_list, conflict, &opts, &file_opts)) < 0) goto done; if (!resolved) { @@ -1899,6 +1996,8 @@ done: if (!given_opts || !given_opts->metric) git__free(opts.metric); + git__free((char *)opts.default_driver); + git_merge_diff_list__free(diff_list); git_iterator_free(empty_ancestor); git_iterator_free(empty_ours); @@ -2111,14 +2210,14 @@ static int merge_annotated_commits( git_iterator *base_iter = NULL, *our_iter = NULL, *their_iter = NULL; int error; - if ((error = compute_base(&base, repo, ours, theirs, opts, + if ((error = compute_base(&base, repo, ours, theirs, opts, recursion_level)) < 0) { - if (error != GIT_ENOTFOUND) - goto done; + if (error != GIT_ENOTFOUND) + goto done; - giterr_clear(); - } + giterr_clear(); + } if ((error = iterator_for_annotated_commit(&base_iter, base)) < 0 || (error = iterator_for_annotated_commit(&our_iter, ours)) < 0 || diff --git a/src/merge.h b/src/merge.h index bd839be49..f8cac161f 100644 --- a/src/merge.h +++ b/src/merge.h @@ -12,8 +12,9 @@ #include "pool.h" #include "iterator.h" -#include "git2/merge.h" #include "git2/types.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" #define GIT_MERGE_MSG_FILE "MERGE_MSG" #define GIT_MERGE_MODE_FILE "MERGE_MODE" @@ -22,6 +23,19 @@ #define GIT_MERGE_DEFAULT_RENAME_THRESHOLD 50 #define GIT_MERGE_DEFAULT_TARGET_LIMIT 1000 + +/** Internal merge flags. */ +enum { + /** The merge is for a virtual base in a recursive merge. */ + GIT_MERGE__VIRTUAL_BASE = (1 << 31), +}; + +enum { + /** Accept the conflict file, staging it as the merge result. */ + GIT_MERGE_FILE_FAVOR__CONFLICTED = 4, +}; + + /** Types of changes when files are merged from branch to branch. */ typedef enum { /* No conflict - a change only occurs in one branch. */ @@ -70,7 +84,6 @@ typedef enum { GIT_MERGE_DIFF_DF_CHILD = (1 << 11), } git_merge_diff_type_t; - typedef struct { git_repository *repo; git_pool pool; @@ -152,4 +165,50 @@ int git_merge__check_result(git_repository *repo, git_index *index_new); int git_merge__append_conflicts_to_merge_msg(git_repository *repo, git_index *index); +/* Merge files */ + +GIT_INLINE(const char *) git_merge_file__best_path( + const char *ancestor, + const char *ours, + const char *theirs) +{ + if (!ancestor) { + if (ours && theirs && strcmp(ours, theirs) == 0) + return ours; + + return NULL; + } + + if (ours && strcmp(ancestor, ours) == 0) + return theirs; + else if(theirs && strcmp(ancestor, theirs) == 0) + return ours; + + return NULL; +} + +GIT_INLINE(uint32_t) git_merge_file__best_mode( + uint32_t ancestor, uint32_t ours, uint32_t theirs) +{ + /* + * If ancestor didn't exist and either ours or theirs is executable, + * assume executable. Otherwise, if any mode changed from the ancestor, + * use that one. + */ + if (!ancestor) { + if (ours == GIT_FILEMODE_BLOB_EXECUTABLE || + theirs == GIT_FILEMODE_BLOB_EXECUTABLE) + return GIT_FILEMODE_BLOB_EXECUTABLE; + + return GIT_FILEMODE_BLOB; + } else if (ours && theirs) { + if (ancestor == ours) + return theirs; + + return ours; + } + + return 0; +} + #endif diff --git a/src/merge_driver.c b/src/merge_driver.c new file mode 100644 index 000000000..88a53ecdb --- /dev/null +++ b/src/merge_driver.c @@ -0,0 +1,397 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "vector.h" +#include "global.h" +#include "merge.h" +#include "merge_driver.h" +#include "git2/merge.h" +#include "git2/sys/merge.h" + +static const char *merge_driver_name__text = "text"; +static const char *merge_driver_name__union = "union"; +static const char *merge_driver_name__binary = "binary"; + +struct merge_driver_registry { + git_rwlock lock; + git_vector drivers; +}; + +typedef struct { + git_merge_driver *driver; + int initialized; + char name[GIT_FLEX_ARRAY]; +} git_merge_driver_entry; + +static struct merge_driver_registry merge_driver_registry; + +static void git_merge_driver_global_shutdown(void); + + +int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + git_merge_driver__builtin *driver = (git_merge_driver__builtin *)self; + git_merge_file_options file_opts = GIT_MERGE_FILE_OPTIONS_INIT; + git_merge_file_result result = {0}; + int error; + + GIT_UNUSED(filter_name); + + if (src->file_opts) + memcpy(&file_opts, src->file_opts, sizeof(git_merge_file_options)); + + if (driver->favor) + file_opts.favor = driver->favor; + + if ((error = git_merge_file_from_index(&result, src->repo, + src->ancestor, src->ours, src->theirs, &file_opts)) < 0) + goto done; + + if (!result.automergeable && + !(file_opts.flags & GIT_MERGE_FILE_FAVOR__CONFLICTED)) { + error = GIT_EMERGECONFLICT; + goto done; + } + + *path_out = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + *mode_out = git_merge_file__best_mode( + src->ancestor ? src->ancestor->mode : 0, + src->ours ? src->ours->mode : 0, + src->theirs ? src->theirs->mode : 0); + + merged_out->ptr = (char *)result.ptr; + merged_out->size = result.len; + merged_out->asize = result.len; + result.ptr = NULL; + +done: + git_merge_file_result_free(&result); + return error; +} + +static int merge_driver_binary_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(self); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static int merge_driver_entry_cmp(const void *a, const void *b) +{ + const git_merge_driver_entry *entry_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(entry_a->name, entry_b->name); +} + +static int merge_driver_entry_search(const void *a, const void *b) +{ + const char *name_a = a; + const git_merge_driver_entry *entry_b = b; + + return strcmp(name_a, entry_b->name); +} + +git_merge_driver__builtin git_merge_driver__text = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_NORMAL +}; + +git_merge_driver__builtin git_merge_driver__union = { + { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + git_merge_driver__builtin_apply, + }, + GIT_MERGE_FILE_FAVOR_UNION +}; + +git_merge_driver git_merge_driver__binary = { + GIT_MERGE_DRIVER_VERSION, + NULL, + NULL, + merge_driver_binary_apply +}; + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_insert( + const char *name, git_merge_driver *driver) +{ + git_merge_driver_entry *entry; + + entry = git__calloc(1, sizeof(git_merge_driver_entry) + strlen(name) + 1); + GITERR_CHECK_ALLOC(entry); + + strcpy(entry->name, name); + entry->driver = driver; + + return git_vector_insert_sorted( + &merge_driver_registry.drivers, entry, NULL); +} + +int git_merge_driver_global_init(void) +{ + int error; + + if (git_rwlock_init(&merge_driver_registry.lock) < 0) + return -1; + + if ((error = git_vector_init(&merge_driver_registry.drivers, 3, + merge_driver_entry_cmp)) < 0) + goto done; + + if ((error = merge_driver_registry_insert( + merge_driver_name__text, &git_merge_driver__text.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__union, &git_merge_driver__union.base)) < 0 || + (error = merge_driver_registry_insert( + merge_driver_name__binary, &git_merge_driver__binary)) < 0) + goto done; + + git__on_shutdown(git_merge_driver_global_shutdown); + +done: + if (error < 0) + git_vector_free_deep(&merge_driver_registry.drivers); + + return error; +} + +static void git_merge_driver_global_shutdown(void) +{ + git_merge_driver_entry *entry; + size_t i; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) + return; + + git_vector_foreach(&merge_driver_registry.drivers, i, entry) { + if (entry->driver->shutdown) + entry->driver->shutdown(entry->driver); + + git__free(entry); + } + + git_vector_free(&merge_driver_registry.drivers); + + git_rwlock_wrunlock(&merge_driver_registry.lock); + git_rwlock_free(&merge_driver_registry.lock); +} + +/* Note: callers must lock the registry before calling this function */ +static int merge_driver_registry_find(size_t *pos, const char *name) +{ + return git_vector_search2(pos, &merge_driver_registry.drivers, + merge_driver_entry_search, name); +} + +/* Note: callers must lock the registry before calling this function */ +static git_merge_driver_entry *merge_driver_registry_lookup( + size_t *pos, const char *name) +{ + git_merge_driver_entry *entry = NULL; + + if (!merge_driver_registry_find(pos, name)) + entry = git_vector_get(&merge_driver_registry.drivers, *pos); + + return entry; +} + +int git_merge_driver_register(const char *name, git_merge_driver *driver) +{ + int error; + + assert(name && driver); + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return -1; + } + + if (!merge_driver_registry_find(NULL, name)) { + giterr_set(GITERR_MERGE, "attempt to reregister existing driver '%s'", + name); + error = GIT_EEXISTS; + goto done; + } + + error = merge_driver_registry_insert(name, driver); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +int git_merge_driver_unregister(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error = 0; + + if (git_rwlock_wrlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return -1; + } + + if ((entry = merge_driver_registry_lookup(&pos, name)) == NULL) { + giterr_set(GITERR_MERGE, "cannot find merge driver '%s' to unregister", + name); + error = GIT_ENOTFOUND; + goto done; + } + + git_vector_remove(&merge_driver_registry.drivers, pos); + + if (entry->initialized && entry->driver->shutdown) { + entry->driver->shutdown(entry->driver); + entry->initialized = false; + } + + git__free(entry); + +done: + git_rwlock_wrunlock(&merge_driver_registry.lock); + return error; +} + +git_merge_driver *git_merge_driver_lookup(const char *name) +{ + git_merge_driver_entry *entry; + size_t pos; + int error; + + /* If we've decided the merge driver to use internally - and not + * based on user configuration (in merge_driver_name_for_path) + * then we can use a hardcoded name to compare instead of bothering + * to take a lock and look it up in the vector. + */ + if (name == merge_driver_name__text) + return &git_merge_driver__text.base; + else if (name == merge_driver_name__binary) + return &git_merge_driver__binary; + + if (git_rwlock_rdlock(&merge_driver_registry.lock) < 0) { + giterr_set(GITERR_OS, "failed to lock merge driver registry"); + return NULL; + } + + entry = merge_driver_registry_lookup(&pos, name); + + git_rwlock_rdunlock(&merge_driver_registry.lock); + + if (entry == NULL) { + giterr_set(GITERR_MERGE, "cannot use an unregistered filter"); + return NULL; + } + + if (!entry->initialized) { + if (entry->driver->initialize && + (error = entry->driver->initialize(entry->driver)) < 0) + return NULL; + + entry->initialized = 1; + } + + return entry->driver; +} + +static int merge_driver_name_for_path( + const char **out, + git_repository *repo, + const char *path, + const char *default_driver) +{ + const char *value; + int error; + + *out = NULL; + + if ((error = git_attr_get(&value, repo, 0, path, "merge")) < 0) + return error; + + /* set: use the built-in 3-way merge driver ("text") */ + if (GIT_ATTR_TRUE(value)) + *out = merge_driver_name__text; + + /* unset: do not merge ("binary") */ + else if (GIT_ATTR_FALSE(value)) + *out = merge_driver_name__binary; + + else if (GIT_ATTR_UNSPECIFIED(value) && default_driver) + *out = default_driver; + + else if (GIT_ATTR_UNSPECIFIED(value)) + *out = merge_driver_name__text; + + else + *out = value; + + return 0; +} + + +GIT_INLINE(git_merge_driver *) merge_driver_lookup_with_wildcard( + const char *name) +{ + git_merge_driver *driver = git_merge_driver_lookup(name); + + if (driver == NULL) + driver = git_merge_driver_lookup("*"); + + return driver; +} + +int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src) +{ + const char *path, *driver_name; + int error = 0; + + path = git_merge_file__best_path( + src->ancestor ? src->ancestor->path : NULL, + src->ours ? src->ours->path : NULL, + src->theirs ? src->theirs->path : NULL); + + if ((error = merge_driver_name_for_path( + &driver_name, src->repo, path, src->default_driver)) < 0) + return error; + + *name_out = driver_name; + *driver_out = merge_driver_lookup_with_wildcard(driver_name); + return error; +} + diff --git a/src/merge_driver.h b/src/merge_driver.h new file mode 100644 index 000000000..bde27502c --- /dev/null +++ b/src/merge_driver.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_merge_driver_h__ +#define INCLUDE_merge_driver_h__ + +#include "git2/merge.h" +#include "git2/index.h" +#include "git2/sys/merge.h" + +struct git_merge_driver_source { + git_repository *repo; + const char *default_driver; + const git_merge_file_options *file_opts; + + const git_index_entry *ancestor; + const git_index_entry *ours; + const git_index_entry *theirs; +}; + +typedef struct git_merge_driver__builtin { + git_merge_driver base; + git_merge_file_favor_t favor; +} git_merge_driver__builtin; + +extern int git_merge_driver_global_init(void); + +extern int git_merge_driver_for_path( + char **name_out, + git_merge_driver **driver_out, + git_repository *repo, + const char *path); + +/* Merge driver configuration */ +extern int git_merge_driver_for_source( + const char **name_out, + git_merge_driver **driver_out, + const git_merge_driver_source *src); + +extern int git_merge_driver__builtin_apply( + git_merge_driver *self, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src); + +/* Merge driver for text files, performs a standard three-way merge */ +extern git_merge_driver__builtin git_merge_driver__text; + +/* Merge driver for union-style merging */ +extern git_merge_driver__builtin git_merge_driver__union; + +/* Merge driver for unmergeable (binary) files: always produces conflicts */ +extern git_merge_driver git_merge_driver__binary; + +#endif diff --git a/src/merge_file.c b/src/merge_file.c index 6d4738065..3f14a4f63 100644 --- a/src/merge_file.c +++ b/src/merge_file.c @@ -11,6 +11,7 @@ #include "fileops.h" #include "index.h" #include "diff_xdiff.h" +#include "merge.h" #include "git2/repository.h" #include "git2/object.h" @@ -26,52 +27,6 @@ #define GIT_MERGE_FILE_SIDE_EXISTS(X) ((X)->mode != 0) -GIT_INLINE(const char *) merge_file_best_path( - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs) -{ - if (!ancestor) { - if (ours && theirs && strcmp(ours->path, theirs->path) == 0) - return ours->path; - - return NULL; - } - - if (ours && strcmp(ancestor->path, ours->path) == 0) - return theirs ? theirs->path : NULL; - else if(theirs && strcmp(ancestor->path, theirs->path) == 0) - return ours ? ours->path : NULL; - - return NULL; -} - -GIT_INLINE(int) merge_file_best_mode( - const git_merge_file_input *ancestor, - const git_merge_file_input *ours, - const git_merge_file_input *theirs) -{ - /* - * If ancestor didn't exist and either ours or theirs is executable, - * assume executable. Otherwise, if any mode changed from the ancestor, - * use that one. - */ - if (!ancestor) { - if ((ours && ours->mode == GIT_FILEMODE_BLOB_EXECUTABLE) || - (theirs && theirs->mode == GIT_FILEMODE_BLOB_EXECUTABLE)) - return GIT_FILEMODE_BLOB_EXECUTABLE; - - return GIT_FILEMODE_BLOB; - } else if (ours && theirs) { - if (ancestor->mode == ours->mode) - return theirs->mode; - - return ours->mode; - } - - return 0; -} - int git_merge_file__input_from_index( git_merge_file_input *input_out, git_odb_object **odb_object_out, @@ -177,8 +132,12 @@ static int merge_file__xdiff( goto done; } - if ((path = merge_file_best_path(ancestor, ours, theirs)) != NULL && - (out->path = strdup(path)) == NULL) { + path = git_merge_file__best_path( + ancestor ? ancestor->path : NULL, + ours->path, + theirs->path); + + if (path != NULL && (out->path = git__strdup(path)) == NULL) { error = -1; goto done; } @@ -186,7 +145,10 @@ static int merge_file__xdiff( out->automergeable = (xdl_result == 0); out->ptr = (const char *)mmbuffer.ptr; out->len = mmbuffer.size; - out->mode = merge_file_best_mode(ancestor, ours, theirs); + out->mode = git_merge_file__best_mode( + ancestor ? ancestor->mode : 0, + ours->mode, + theirs->mode); done: if (error < 0) diff --git a/src/netops.c b/src/netops.c index c4241989f..90326ea59 100644 --- a/src/netops.c +++ b/src/netops.c @@ -257,16 +257,18 @@ int gitno_extract_url_parts( *port = git__strdup(default_port); GITERR_CHECK_ALLOC(*port); - if (u.field_set & (1 << UF_PATH)) { - *path = git__substrdup(_path, u.field_data[UF_PATH].len); - GITERR_CHECK_ALLOC(*path); - } else { - git__free(*port); - *port = NULL; - git__free(*host); - *host = NULL; - giterr_set(GITERR_NET, "invalid url, missing path"); - return GIT_EINVALIDSPEC; + if (path) { + if (u.field_set & (1 << UF_PATH)) { + *path = git__substrdup(_path, u.field_data[UF_PATH].len); + GITERR_CHECK_ALLOC(*path); + } else { + git__free(*port); + *port = NULL; + git__free(*host); + *host = NULL; + giterr_set(GITERR_NET, "invalid url, missing path"); + return GIT_EINVALIDSPEC; + } } if (u.field_set & (1 << UF_USERINFO)) { diff --git a/src/object_api.c b/src/object_api.c index 838bba323..e0d8760e7 100644 --- a/src/object_api.c +++ b/src/object_api.c @@ -15,7 +15,7 @@ #include "tag.h" /** - * Blob + * Commit */ int git_commit_lookup(git_commit **out, git_repository *repo, const git_oid *id) { @@ -42,6 +42,10 @@ git_repository *git_commit_owner(const git_commit *obj) return git_object_owner((const git_object *)obj); } +int git_commit_dup(git_commit **out, git_commit *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} /** * Tree @@ -71,6 +75,10 @@ git_repository *git_tree_owner(const git_tree *obj) return git_object_owner((const git_object *)obj); } +int git_tree_dup(git_tree **out, git_tree *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} /** * Tag @@ -100,6 +108,11 @@ git_repository *git_tag_owner(const git_tag *obj) return git_object_owner((const git_object *)obj); } +int git_tag_dup(git_tag **out, git_tag *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} + /** * Blob */ @@ -127,3 +140,8 @@ git_repository *git_blob_owner(const git_blob *obj) { return git_object_owner((const git_object *)obj); } + +int git_blob_dup(git_blob **out, git_blob *obj) +{ + return git_object_dup((git_object **)out, (git_object *)obj); +} diff --git a/src/odb.c b/src/odb.c index 1ecfe937f..7b194c70f 100644 --- a/src/odb.c +++ b/src/odb.c @@ -12,12 +12,13 @@ #include "fileops.h" #include "hash.h" #include "odb.h" -#include "delta-apply.h" +#include "delta.h" #include "filter.h" #include "repository.h" #include "git2/odb_backend.h" #include "git2/oid.h" +#include "git2/oidarray.h" #define GIT_ALTERNATES_FILE "info/alternates" @@ -48,8 +49,32 @@ static git_cache *odb_cache(git_odb *odb) return &odb->own_cache; } +static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id); static int load_alternates(git_odb *odb, const char *objects_dir, int alternate_depth); +static git_otype odb_hardcoded_type(const git_oid *id) +{ + static git_oid empty_tree = {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, + 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; + + if (!git_oid_cmp(id, &empty_tree)) + return GIT_OBJ_TREE; + + return GIT_OBJ_BAD; +} + +static int odb_read_hardcoded(git_rawobj *raw, const git_oid *id) +{ + git_otype type = odb_hardcoded_type(id); + if (type == GIT_OBJ_BAD) + return -1; + + raw->type = type; + raw->len = 0; + raw->data = git__calloc(1, sizeof(uint8_t)); + return 0; +} + int git_odb__format_object_header(char *hdr, size_t n, git_off_t obj_len, git_otype obj_type) { const char *type_str = git_object_type2string(obj_type); @@ -624,7 +649,10 @@ void git_odb_free(git_odb *db) GIT_REFCOUNT_DEC(db, odb_free); } -static int odb_exists_1(git_odb *db, const git_oid *id, bool only_refreshed) +static int odb_exists_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) { size_t i; bool found = false; @@ -643,6 +671,44 @@ static int odb_exists_1(git_odb *db, const git_oid *id, bool only_refreshed) return (int)found; } +static int odb_freshen_1( + git_odb *db, + const git_oid *id, + bool only_refreshed) +{ + size_t i; + bool found = false; + + for (i = 0; i < db->backends.length && !found; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (b->freshen != NULL) + found = !b->freshen(b, id); + else if (b->exists != NULL) + found = b->exists(b, id); + } + + return (int)found; +} + +static int odb_freshen(git_odb *db, const git_oid *id) +{ + assert(db && id); + + if (odb_freshen_1(db, id, false)) + return 1; + + if (!git_odb_refresh(db)) + return odb_freshen_1(db, id, true); + + /* Failed to refresh, hence not found */ + return 0; +} + int git_odb_exists(git_odb *db, const git_oid *id) { git_odb_object *object; @@ -651,7 +717,7 @@ int git_odb_exists(git_odb *db, const git_oid *id) if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { git_odb_object_free(object); - return (int)true; + return 1; } if (odb_exists_1(db, id, false)) @@ -716,10 +782,8 @@ int git_odb_exists_prefix( if (len < GIT_OID_MINPREFIXLEN) return git_odb__error_ambiguous("prefix length too short"); - if (len > GIT_OID_HEXSZ) - len = GIT_OID_HEXSZ; - if (len == GIT_OID_HEXSZ) { + if (len >= GIT_OID_HEXSZ) { if (git_odb_exists(db, short_id)) { if (out) git_oid_cpy(out, short_id); @@ -730,10 +794,7 @@ int git_odb_exists_prefix( } } - /* just copy valid part of short_id */ - memcpy(&key.id, short_id->id, (len + 1) / 2); - if (len & 1) - key.id[len / 2] &= 0xF0; + git_oid__cpy_prefix(&key, short_id, len); error = odb_exists_prefix_1(out, db, &key, len, false); @@ -746,6 +807,72 @@ int git_odb_exists_prefix( return error; } +int git_odb_expand_ids( + git_odb *db, + git_odb_expand_id *ids, + size_t count) +{ + size_t i; + + assert(db && ids); + + for (i = 0; i < count; i++) { + git_odb_expand_id *query = &ids[i]; + int error = GIT_EAMBIGUOUS; + + if (!query->type) + query->type = GIT_OBJ_ANY; + + /* if we have a short OID, expand it first */ + if (query->length >= GIT_OID_MINPREFIXLEN && query->length < GIT_OID_HEXSZ) { + git_oid actual_id; + + error = odb_exists_prefix_1(&actual_id, db, &query->id, query->length, false); + if (!error) { + git_oid_cpy(&query->id, &actual_id); + query->length = GIT_OID_HEXSZ; + } + } + + /* + * now we ought to have a 40-char OID, either because we've expanded it + * or because the user passed a full OID. Ensure its type is right. + */ + if (query->length >= GIT_OID_HEXSZ) { + git_otype actual_type; + + error = odb_otype_fast(&actual_type, db, &query->id); + if (!error) { + if (query->type != GIT_OBJ_ANY && query->type != actual_type) + error = GIT_ENOTFOUND; + else + query->type = actual_type; + } + } + + switch (error) { + /* no errors, so we've successfully expanded the OID */ + case 0: + continue; + + /* the object is missing or ambiguous */ + case GIT_ENOTFOUND: + case GIT_EAMBIGUOUS: + memset(&query->id, 0, sizeof(git_oid)); + query->length = 0; + query->type = 0; + break; + + /* something went very wrong with the ODB; bail hard */ + default: + return error; + } + } + + giterr_clear(); + return 0; +} + int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git_oid *id) { int error; @@ -759,11 +886,53 @@ int git_odb_read_header(size_t *len_p, git_otype *type_p, git_odb *db, const git return error; } +static int odb_read_header_1( + size_t *len_p, git_otype *type_p, git_odb *db, + const git_oid *id, bool only_refreshed) +{ + size_t i; + git_otype ht; + bool passthrough = false; + int error; + + if (!only_refreshed && (ht = odb_hardcoded_type(id)) != GIT_OBJ_BAD) { + *type_p = ht; + *len_p = 0; + return 0; + } + + for (i = 0; i < db->backends.length; ++i) { + backend_internal *internal = git_vector_get(&db->backends, i); + git_odb_backend *b = internal->backend; + + if (only_refreshed && !b->refresh) + continue; + + if (!b->read_header) { + passthrough = true; + continue; + } + + error = b->read_header(len_p, type_p, b, id); + + switch (error) { + case GIT_PASSTHROUGH: + passthrough = true; + break; + case GIT_ENOTFOUND: + break; + default: + return error; + } + } + + return passthrough ? GIT_PASSTHROUGH : GIT_ENOTFOUND; +} + 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) { - size_t i; int error = GIT_ENOTFOUND; git_odb_object *object; @@ -777,45 +946,32 @@ int git_odb__read_header_or_object( } *out = NULL; + error = odb_read_header_1(len_p, type_p, db, id, false); - 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; + if (error == GIT_ENOTFOUND && !git_odb_refresh(db)) + error = odb_read_header_1(len_p, type_p, db, id, true); - if (b->read_header != NULL) - error = b->read_header(len_p, type_p, b, id); - } + if (error == GIT_ENOTFOUND) + return git_odb__error_notfound("cannot read header for", id, GIT_OID_HEXSZ); - if (!error || error == GIT_PASSTHROUGH) + /* we found the header; return early */ + if (!error) return 0; - /* - * no backend could read only the header. - * try reading the whole object and freeing the contents - */ - if ((error = git_odb_read(&object, db, id)) < 0) - return error; /* error already set - pass along */ - - *len_p = object->cached.size; - *type_p = object->cached.type; - *out = object; - - return 0; -} - -static git_oid empty_tree = {{ 0x4b, 0x82, 0x5d, 0xc6, 0x42, 0xcb, 0x6e, 0xb9, 0xa0, 0x60, - 0xe5, 0x4b, 0xf8, 0xd6, 0x92, 0x88, 0xfb, 0xee, 0x49, 0x04 }}; - -static int hardcoded_objects(git_rawobj *raw, const git_oid *id) -{ - if (!git_oid_cmp(id, &empty_tree)) { - raw->type = GIT_OBJ_TREE; - raw->len = 0; - raw->data = git__calloc(1, sizeof(uint8_t)); - return 0; - } else { - return GIT_ENOTFOUND; + if (error == GIT_PASSTHROUGH) { + /* + * no backend has header-reading functionality + * so try using `git_odb_read` instead + */ + error = git_odb_read(&object, db, id); + if (!error) { + *len_p = object->cached.size; + *type_p = object->cached.type; + *out = object; + } } + + return error; } static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, @@ -826,7 +982,7 @@ static int odb_read_1(git_odb_object **out, git_odb *db, const git_oid *id, git_odb_object *object; bool found = false; - if (!hardcoded_objects(&raw, id)) + if (!only_refreshed && odb_read_hardcoded(&raw, id) == 0) found = true; for (i = 0; i < db->backends.length && !found; ++i) { @@ -880,6 +1036,29 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) return error; } +static int odb_otype_fast(git_otype *type_p, git_odb *db, const git_oid *id) +{ + git_odb_object *object; + size_t _unused; + int error; + + if ((object = git_cache_get_raw(odb_cache(db), id)) != NULL) { + *type_p = object->cached.type; + return 0; + } + + error = odb_read_header_1(&_unused, type_p, db, id, false); + + if (error == GIT_PASSTHROUGH) { + error = odb_read_1(&object, db, id, false); + if (!error) + *type_p = object->cached.type; + git_odb_object_free(object); + } + + return error; +} + static int read_prefix_1(git_odb_object **out, git_odb *db, const git_oid *key, size_t len, bool only_refreshed) { @@ -950,10 +1129,7 @@ int git_odb_read_prefix( return 0; } - /* just copy valid part of short_id */ - memcpy(&key.id, short_id->id, (len + 1) / 2); - if (len & 1) - key.id[len / 2] &= 0xF0; + git_oid__cpy_prefix(&key, short_id, len); error = read_prefix_1(out, db, &key, len, false); @@ -991,7 +1167,7 @@ int git_odb_write( assert(oid && db); git_odb_hash(oid, data, len, type); - if (git_odb_exists(db, oid)) + if (odb_freshen(db, oid)) return 0; for (i = 0; i < db->backends.length && error < 0; ++i) { @@ -1117,7 +1293,7 @@ int git_odb_stream_finalize_write(git_oid *out, git_odb_stream *stream) git_hash_final(out, stream->hash_ctx); - if (git_odb_exists(stream->backend->odb, out)) + if (odb_freshen(stream->backend->odb, out)) return 0; return stream->finalize_write(stream, out); @@ -1224,7 +1400,7 @@ int git_odb__error_notfound( char oid_str[GIT_OID_HEXSZ + 1]; git_oid_tostr(oid_str, oid_len+1, oid); giterr_set(GITERR_ODB, "Object not found - %s (%.*s)", - message, oid_len, oid_str); + message, (int) oid_len, oid_str); } else giterr_set(GITERR_ODB, "Object not found - %s", message); diff --git a/src/odb_loose.c b/src/odb_loose.c index 3c33160d0..f312b9c9c 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -12,7 +12,7 @@ #include "fileops.h" #include "hash.h" #include "odb.h" -#include "delta-apply.h" +#include "delta.h" #include "filebuf.h" #include "git2/odb_backend.h" @@ -918,6 +918,23 @@ cleanup: return error; } +static int loose_backend__freshen( + git_odb_backend *_backend, + const git_oid *oid) +{ + loose_backend *backend = (loose_backend *)_backend; + git_buf path = GIT_BUF_INIT; + int error; + + if (object_file_name(&path, backend, oid) < 0) + return -1; + + error = git_futils_touch(path.ptr, NULL); + git_buf_free(&path); + + return error; +} + static void loose_backend__free(git_odb_backend *_backend) { loose_backend *backend; @@ -975,6 +992,7 @@ int git_odb_backend_loose( backend->parent.exists = &loose_backend__exists; backend->parent.exists_prefix = &loose_backend__exists_prefix; backend->parent.foreach = &loose_backend__foreach; + backend->parent.freshen = &loose_backend__freshen; backend->parent.free = &loose_backend__free; *backend_out = (git_odb_backend *)backend; diff --git a/src/odb_mempack.c b/src/odb_mempack.c index 594a2784c..68db3bc32 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -24,7 +24,7 @@ struct memobject { git_oid oid; size_t len; git_otype type; - char data[]; + char data[GIT_FLEX_ARRAY]; }; struct memory_packer_db { diff --git a/src/odb_pack.c b/src/odb_pack.c index 0764207e5..b80d0337a 100644 --- a/src/odb_pack.c +++ b/src/odb_pack.c @@ -13,13 +13,16 @@ #include "fileops.h" #include "hash.h" #include "odb.h" -#include "delta-apply.h" +#include "delta.h" #include "sha1_lookup.h" #include "mwindow.h" #include "pack.h" #include "git2/odb_backend.h" +/* re-freshen pack files no more than every 2 seconds */ +#define FRESHEN_FREQUENCY 2 + struct pack_backend { git_odb_backend parent; git_vector packs; @@ -363,6 +366,28 @@ static int pack_backend__read_header( return git_packfile_resolve_header(len_p, type_p, e.p, e.offset); } +static int pack_backend__freshen( + git_odb_backend *backend, const git_oid *oid) +{ + struct git_pack_entry e; + time_t now; + int error; + + if ((error = pack_entry_find(&e, (struct pack_backend *)backend, oid)) < 0) + return error; + + now = time(NULL); + + if (e.p->last_freshen > now - FRESHEN_FREQUENCY) + return 0; + + if ((error = git_futils_touch(e.p->pack_name, &now)) < 0) + return error; + + e.p->last_freshen = now; + return 0; +} + static int pack_backend__read( void **buffer_p, size_t *len_p, git_otype *type_p, git_odb_backend *backend, const git_oid *oid) @@ -560,6 +585,7 @@ static int pack_backend__alloc(struct pack_backend **out, size_t initial_size) backend->parent.refresh = &pack_backend__refresh; backend->parent.foreach = &pack_backend__foreach; backend->parent.writepack = &pack_backend__writepack; + backend->parent.freshen = &pack_backend__freshen; backend->parent.free = &pack_backend__free; *out = backend; diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 64187a8a2..ddb9c4ab9 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -164,7 +164,7 @@ int git_openssl_set_locking(void) git__on_shutdown(shutdown_ssl_locking); return 0; #else - giterr_set(GITERR_THREAD, "libgit2 as not built with threads"); + giterr_set(GITERR_THREAD, "libgit2 was not built with threads"); return -1; #endif } @@ -515,11 +515,11 @@ int openssl_certificate(git_cert **out, git_stream *stream) return 0; } -static int openssl_set_proxy(git_stream *stream, const char *proxy_url) +static int openssl_set_proxy(git_stream *stream, const git_proxy_options *proxy_opts) { openssl_stream *st = (openssl_stream *) stream; - return git_stream_set_proxy(st->io, proxy_url); + return git_stream_set_proxy(st->io, proxy_opts); } ssize_t openssl_write(git_stream *stream, const char *data, size_t len, int flags) @@ -541,9 +541,8 @@ ssize_t openssl_read(git_stream *stream, void *data, size_t len) openssl_stream *st = (openssl_stream *) stream; int ret; - if ((ret = SSL_read(st->ssl, data, len)) <= 0) { + if ((ret = SSL_read(st->ssl, data, len)) <= 0) return ssl_set_error(st->ssl, ret); - } return ret; } diff --git a/src/pack-objects.c b/src/pack-objects.c index 29231e028..9f62322f7 100644 --- a/src/pack-objects.c +++ b/src/pack-objects.c @@ -28,7 +28,7 @@ struct unpacked { git_pobject *object; void *data; struct git_delta_index *index; - int depth; + size_t depth; }; struct tree_walk_context { @@ -99,8 +99,15 @@ static int packbuilder_config(git_packbuilder *pb) #define config_get(KEY,DST,DFLT) do { \ ret = git_config_get_int64(&val, config, KEY); \ - if (!ret) (DST) = val; \ - else if (ret == GIT_ENOTFOUND) { \ + if (!ret) { \ + if (!git__is_sizet(val)) { \ + giterr_set(GITERR_CONFIG, \ + "configuration value '%s' is too large", KEY); \ + ret = -1; \ + goto out; \ + } \ + (DST) = (size_t)val; \ + } else if (ret == GIT_ENOTFOUND) { \ (DST) = (DFLT); \ ret = 0; \ } else if (ret < 0) goto out; } while (0) @@ -144,7 +151,7 @@ int git_packbuilder_new(git_packbuilder **out, git_repository *repo) pb->nr_threads = 1; /* do not spawn any thread by default */ if (git_hash_ctx_init(&pb->ctx) < 0 || - git_zstream_init(&pb->zstream) < 0 || + git_zstream_init(&pb->zstream, GIT_ZSTREAM_DEFLATE) < 0 || git_repository_odb(&pb->odb, repo) < 0 || packbuilder_config(pb) < 0) goto on_error; @@ -187,7 +194,7 @@ static void rehash(git_packbuilder *pb) { git_pobject *po; khiter_t pos; - unsigned int i; + size_t i; int ret; kh_clear(oid, pb->object_ix); @@ -222,7 +229,7 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, return -1; } - pb->nr_alloc = (uint32_t)newsize; + pb->nr_alloc = newsize; pb->object_list = git__reallocarray(pb->object_list, pb->nr_alloc, sizeof(*po)); @@ -272,8 +279,9 @@ int git_packbuilder_insert(git_packbuilder *pb, const git_oid *oid, static int get_delta(void **out, git_odb *odb, git_pobject *po) { git_odb_object *src = NULL, *trg = NULL; - unsigned long delta_size; + size_t delta_size; void *delta_buf; + int error; *out = NULL; @@ -281,12 +289,15 @@ static int get_delta(void **out, git_odb *odb, git_pobject *po) git_odb_read(&trg, odb, &po->id) < 0) goto on_error; - delta_buf = git_delta( - git_odb_object_data(src), (unsigned long)git_odb_object_size(src), - git_odb_object_data(trg), (unsigned long)git_odb_object_size(trg), - &delta_size, 0); + error = git_delta(&delta_buf, &delta_size, + git_odb_object_data(src), git_odb_object_size(src), + git_odb_object_data(trg), git_odb_object_size(trg), + 0); - if (!delta_buf || delta_size != po->delta_size) { + if (error < 0 && error != GIT_EBUFS) + goto on_error; + + if (error == GIT_EBUFS || delta_size != po->delta_size) { giterr_set(GITERR_INVALID, "Delta size changed"); goto on_error; } @@ -437,8 +448,8 @@ static int write_one( return write_object(pb, po, write_cb, cb_data); } -GIT_INLINE(void) add_to_write_order(git_pobject **wo, unsigned int *endp, - git_pobject *po) +GIT_INLINE(void) add_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) { if (po->filled) return; @@ -446,8 +457,8 @@ GIT_INLINE(void) add_to_write_order(git_pobject **wo, unsigned int *endp, po->filled = 1; } -static void add_descendants_to_write_order(git_pobject **wo, unsigned int *endp, - git_pobject *po) +static void add_descendants_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) { int add_to_order = 1; while (po) { @@ -488,8 +499,8 @@ static void add_descendants_to_write_order(git_pobject **wo, unsigned int *endp, }; } -static void add_family_to_write_order(git_pobject **wo, unsigned int *endp, - git_pobject *po) +static void add_family_to_write_order(git_pobject **wo, size_t *endp, + git_pobject *po) { git_pobject *root; @@ -520,7 +531,7 @@ static int cb_tag_foreach(const char *name, git_oid *oid, void *data) static git_pobject **compute_write_order(git_packbuilder *pb) { - unsigned int i, wo_end, last_untagged; + size_t i, wo_end, last_untagged; git_pobject **wo; if ((wo = git__mallocarray(pb->nr_objects, sizeof(*wo))) == NULL) @@ -625,13 +636,18 @@ static int write_pack(git_packbuilder *pb, enum write_one_status status; struct git_pack_header ph; git_oid entry_oid; - unsigned int i = 0; + size_t i = 0; int error = 0; write_order = compute_write_order(pb); if (write_order == NULL) return -1; + if (!git__is_uint32(pb->nr_objects)) { + giterr_set(GITERR_INVALID, "too many objects"); + return -1; + } + /* Write pack header */ ph.hdr_signature = htonl(PACK_SIGNATURE); ph.hdr_version = htonl(PACK_VERSION); @@ -707,11 +723,18 @@ static int type_size_sort(const void *_a, const void *_b) return a < b ? -1 : (a > b); /* newest first */ } -static int delta_cacheable(git_packbuilder *pb, unsigned long src_size, - unsigned long trg_size, unsigned long delta_size) +static int delta_cacheable( + git_packbuilder *pb, + size_t src_size, + size_t trg_size, + size_t delta_size) { - if (pb->max_delta_cache_size && - pb->delta_cache_size + delta_size > pb->max_delta_cache_size) + size_t new_size; + + if (git__add_sizet_overflow(&new_size, pb->delta_cache_size, delta_size)) + return 0; + + if (pb->max_delta_cache_size && new_size > pb->max_delta_cache_size) return 0; if (delta_size < pb->cache_max_small_delta_size) @@ -725,15 +748,14 @@ static int delta_cacheable(git_packbuilder *pb, unsigned long src_size, } static int try_delta(git_packbuilder *pb, struct unpacked *trg, - struct unpacked *src, int max_depth, - unsigned long *mem_usage, int *ret) + struct unpacked *src, size_t max_depth, + size_t *mem_usage, int *ret) { git_pobject *trg_object = trg->object; git_pobject *src_object = src->object; git_odb_object *obj; - unsigned long trg_size, src_size, delta_size, - sizediff, max_size, sz; - unsigned int ref_depth; + size_t trg_size, src_size, delta_size, sizediff, max_size, sz; + size_t ref_depth; void *delta_buf; /* Don't bother doing diffs between different types */ @@ -751,7 +773,7 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, return 0; /* Now some size filtering heuristics. */ - trg_size = (unsigned long)trg_object->size; + trg_size = trg_object->size; if (!trg_object->delta) { max_size = trg_size/2 - 20; ref_depth = 1; @@ -765,7 +787,7 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, if (max_size == 0) return 0; - src_size = (unsigned long)src_object->size; + src_size = src_object->size; sizediff = src_size < trg_size ? trg_size - src_size : 0; if (sizediff >= max_size) return 0; @@ -777,7 +799,7 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, if (git_odb_read(&obj, pb->odb, &trg_object->id) < 0) return -1; - sz = (unsigned long)git_odb_object_size(obj); + sz = git_odb_object_size(obj); trg->data = git__malloc(sz); GITERR_CHECK_ALLOC(trg->data); memcpy(trg->data, git_odb_object_data(obj), sz); @@ -799,7 +821,7 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, !git__is_ulong(obj_sz = git_odb_object_size(obj))) return -1; - sz = (unsigned long)obj_sz; + sz = obj_sz; src->data = git__malloc(sz); GITERR_CHECK_ALLOC(src->data); memcpy(src->data, git_odb_object_data(obj), sz); @@ -815,16 +837,14 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, *mem_usage += sz; } if (!src->index) { - src->index = git_delta_create_index(src->data, src_size); - if (!src->index) + if (git_delta_index_init(&src->index, src->data, src_size) < 0) return 0; /* suboptimal pack - out of memory */ - *mem_usage += git_delta_sizeof_index(src->index); + *mem_usage += git_delta_index_size(src->index); } - delta_buf = git_delta_create(src->index, trg->data, trg_size, - &delta_size, max_size); - if (!delta_buf) + if (git_delta_create_from_index(&delta_buf, &delta_size, src->index, trg->data, trg_size, + max_size) < 0) return 0; if (trg_object->delta) { @@ -839,11 +859,12 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, git_packbuilder__cache_lock(pb); if (trg_object->delta_data) { git__free(trg_object->delta_data); + assert(pb->delta_cache_size >= trg_object->delta_size); pb->delta_cache_size -= trg_object->delta_size; trg_object->delta_data = NULL; } if (delta_cacheable(pb, src_size, trg_size, delta_size)) { - bool overflow = git__add_uint64_overflow( + bool overflow = git__add_sizet_overflow( &pb->delta_cache_size, pb->delta_cache_size, delta_size); git_packbuilder__cache_unlock(pb); @@ -869,13 +890,13 @@ static int try_delta(git_packbuilder *pb, struct unpacked *trg, return 0; } -static unsigned int check_delta_limit(git_pobject *me, unsigned int n) +static size_t check_delta_limit(git_pobject *me, size_t n) { git_pobject *child = me->delta_child; - unsigned int m = n; + size_t m = n; while (child) { - unsigned int c = check_delta_limit(child, n + 1); + size_t c = check_delta_limit(child, n + 1); if (m < c) m = c; child = child->delta_sibling; @@ -883,13 +904,18 @@ static unsigned int check_delta_limit(git_pobject *me, unsigned int n) return m; } -static unsigned long free_unpacked(struct unpacked *n) +static size_t free_unpacked(struct unpacked *n) { - unsigned long freed_mem = git_delta_sizeof_index(n->index); - git_delta_free_index(n->index); + size_t freed_mem = 0; + + if (n->index) { + freed_mem += git_delta_index_size(n->index); + git_delta_index_free(n->index); + } n->index = NULL; + if (n->data) { - freed_mem += (unsigned long)n->object->size; + freed_mem += n->object->size; git__free(n->data); n->data = NULL; } @@ -898,7 +924,8 @@ static unsigned long free_unpacked(struct unpacked *n) return freed_mem; } -static int report_delta_progress(git_packbuilder *pb, uint32_t count, bool force) +static int report_delta_progress( + git_packbuilder *pb, uint32_t count, bool force) { int ret; @@ -922,15 +949,14 @@ static int report_delta_progress(git_packbuilder *pb, uint32_t count, bool force } static int find_deltas(git_packbuilder *pb, git_pobject **list, - unsigned int *list_size, unsigned int window, - int depth) + size_t *list_size, size_t window, size_t depth) { git_pobject *po; git_buf zbuf = GIT_BUF_INIT; struct unpacked *array; - uint32_t idx = 0, count = 0; - unsigned long mem_usage = 0; - unsigned int i; + size_t idx = 0, count = 0; + size_t mem_usage = 0; + size_t i; int error = -1; array = git__calloc(window, sizeof(struct unpacked)); @@ -938,7 +964,7 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, for (;;) { struct unpacked *n = array + idx; - int max_depth, j, best_base = -1; + size_t max_depth, j, best_base = SIZE_MAX; git_packbuilder__progress_lock(pb); if (!*list_size) { @@ -959,7 +985,7 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, while (pb->window_memory_limit && mem_usage > pb->window_memory_limit && count > 1) { - uint32_t tail = (idx + window - count) % window; + size_t tail = (idx + window - count) % window; mem_usage -= free_unpacked(array + tail); count--; } @@ -971,15 +997,18 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, */ max_depth = depth; if (po->delta_child) { - max_depth -= check_delta_limit(po, 0); - if (max_depth <= 0) + size_t delta_limit = check_delta_limit(po, 0); + + if (delta_limit > max_depth) goto next; + + max_depth -= delta_limit; } j = window; while (--j > 0) { int ret; - uint32_t other_idx = idx + j; + size_t other_idx = idx + j; struct unpacked *m; if (other_idx >= window) @@ -1020,7 +1049,7 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, GITERR_CHECK_ALLOC(po->delta_data); memcpy(po->delta_data, zbuf.ptr, zbuf.size); - po->z_delta_size = (unsigned long)zbuf.size; + po->z_delta_size = zbuf.size; git_buf_clear(&zbuf); git_packbuilder__cache_lock(pb); @@ -1044,10 +1073,10 @@ static int find_deltas(git_packbuilder *pb, git_pobject **list, */ if (po->delta) { struct unpacked swap = array[best_base]; - int dist = (window + idx - best_base) % window; - int dst = best_base; + size_t dist = (window + idx - best_base) % window; + size_t dst = best_base; while (dist--) { - int src = (dst + 1) % window; + size_t src = (dst + 1) % window; array[dst] = array[src]; dst = src; } @@ -1085,13 +1114,13 @@ struct thread_params { git_cond cond; git_mutex mutex; - unsigned int list_size; - unsigned int remaining; + size_t list_size; + size_t remaining; - int window; - int depth; - int working; - int data_ready; + size_t window; + size_t depth; + size_t working; + size_t data_ready; }; static void *threaded_find_deltas(void *arg) @@ -1133,11 +1162,11 @@ static void *threaded_find_deltas(void *arg) } static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, - unsigned int list_size, unsigned int window, - int depth) + size_t list_size, size_t window, size_t depth) { struct thread_params *p; - int i, ret, active_threads = 0; + size_t i; + int ret, active_threads = 0; if (!pb->nr_threads) pb->nr_threads = git_online_cpus(); @@ -1152,7 +1181,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, /* Partition the work among the threads */ for (i = 0; i < pb->nr_threads; ++i) { - unsigned sub_size = list_size / (pb->nr_threads - i); + size_t sub_size = list_size / (pb->nr_threads - i); /* don't use too small segments or no deltas will be found */ if (sub_size < 2*window && i+1 < pb->nr_threads) @@ -1206,7 +1235,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, while (active_threads) { struct thread_params *target = NULL; struct thread_params *victim = NULL; - unsigned sub_size = 0; + size_t sub_size = 0; /* Start by locating a thread that has transitioned its * 'working' flag from 1 -> 0. This indicates that it is @@ -1286,7 +1315,7 @@ static int ll_find_deltas(git_packbuilder *pb, git_pobject **list, static int prepare_pack(git_packbuilder *pb) { git_pobject **delta_list; - unsigned int i, n = 0; + size_t i, n = 0; if (pb->nr_objects == 0 || pb->done) return 0; /* nothing to do */ @@ -1473,12 +1502,12 @@ cleanup: return error; } -uint32_t git_packbuilder_object_count(git_packbuilder *pb) +size_t git_packbuilder_object_count(git_packbuilder *pb) { return pb->nr_objects; } -uint32_t git_packbuilder_written(git_packbuilder *pb) +size_t git_packbuilder_written(git_packbuilder *pb) { return pb->nr_written; } diff --git a/src/pack-objects.h b/src/pack-objects.h index 82dea81f5..5a84f4158 100644 --- a/src/pack-objects.h +++ b/src/pack-objects.h @@ -42,8 +42,8 @@ typedef struct git_pobject { * me */ void *delta_data; - unsigned long delta_size; - unsigned long z_delta_size; + size_t delta_size; + size_t z_delta_size; int written:1, recursing:1, @@ -65,10 +65,11 @@ struct git_packbuilder { git_zstream zstream; uint32_t nr_objects, - nr_deltified, - nr_alloc, - nr_written, - nr_remaining; + nr_deltified, + nr_written, + nr_remaining; + + size_t nr_alloc; git_pobject *object_list; @@ -85,13 +86,13 @@ struct git_packbuilder { git_cond progress_cond; /* configs */ - uint64_t delta_cache_size; - uint64_t max_delta_cache_size; - uint64_t cache_max_small_delta_size; - uint64_t big_file_threshold; - uint64_t window_memory_limit; + size_t delta_cache_size; + size_t max_delta_cache_size; + size_t cache_max_small_delta_size; + size_t big_file_threshold; + size_t window_memory_limit; - int nr_threads; /* nr of threads to use */ + unsigned int nr_threads; /* nr of threads to use */ git_packbuilder_progress progress_cb; void *progress_cb_payload; diff --git a/src/pack.c b/src/pack.c index 6a700e29f..2e19b3b62 100644 --- a/src/pack.c +++ b/src/pack.c @@ -8,7 +8,7 @@ #include "common.h" #include "odb.h" #include "pack.h" -#include "delta-apply.h" +#include "delta.h" #include "sha1_lookup.h" #include "mwindow.h" #include "fileops.h" @@ -505,12 +505,14 @@ int git_packfile_resolve_header( git_mwindow_close(&w_curs); if ((error = git_packfile_stream_open(&stream, p, curpos)) < 0) return error; - error = git__delta_read_header_fromstream(&base_size, size_p, &stream); + error = git_delta_read_header_fromstream(&base_size, size_p, &stream); git_packfile_stream_free(&stream); if (error < 0) return error; - } else + } else { *size_p = size; + base_offset = 0; + } while (type == GIT_OBJ_OFS_DELTA || type == GIT_OBJ_REF_DELTA) { curpos = base_offset; @@ -730,8 +732,9 @@ int git_packfile_unpack( obj->len = 0; obj->type = GIT_OBJ_BAD; - error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + error = git_delta_apply(&obj->data, &obj->len, base.data, base.len, delta.data, delta.len); obj->type = base_type; + /* * We usually don't want to free the base at this * point, as we put it into the cache in the previous @@ -756,8 +759,11 @@ int git_packfile_unpack( } cleanup: - if (error < 0) + if (error < 0) { git__free(obj->data); + if (cached) + git_atomic_dec(&cached->refcount); + } if (elem) *obj_offset = curpos; @@ -1267,8 +1273,8 @@ static int pack_entry_find_offset( const git_oid *short_oid, size_t len) { - const uint32_t *level1_ofs = p->index_map.data; - const unsigned char *index = p->index_map.data; + const uint32_t *level1_ofs; + const unsigned char *index; unsigned hi, lo, stride; int pos, found = 0; git_off_t offset; @@ -1282,11 +1288,11 @@ static int pack_entry_find_offset( if ((error = pack_index_open(p)) < 0) return error; assert(p->index_map.data); - - index = p->index_map.data; - level1_ofs = p->index_map.data; } + index = p->index_map.data; + level1_ofs = p->index_map.data; + if (p->index_version > 1) { level1_ofs += 2; index += 8; diff --git a/src/pack.h b/src/pack.h index d15247b74..5302db5b7 100644 --- a/src/pack.h +++ b/src/pack.h @@ -102,6 +102,8 @@ struct git_pack_file { git_pack_cache bases; /* delta base cache */ + time_t last_freshen; /* last time the packfile was freshened */ + /* something like ".git/objects/pack/xxxxx.pack" */ char pack_name[GIT_FLEX_ARRAY]; /* more */ }; diff --git a/src/patch.c b/src/patch.c new file mode 100644 index 000000000..9b7c9c64c --- /dev/null +++ b/src/patch.c @@ -0,0 +1,216 @@ +#include "git2/patch.h" +#include "diff.h" +#include "patch.h" + + +int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload) +{ + int error = 0; + uint32_t i, j; + + if (file_cb) + error = file_cb(patch->delta, 0, payload); + + if (error) + return error; + + if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if (binary_cb) + error = binary_cb(patch->delta, &patch->binary, payload); + + return error; + } + + if (!hunk_cb && !line_cb) + return error; + + for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { + git_patch_hunk *h = git_array_get(patch->hunks, i); + + if (hunk_cb) + error = hunk_cb(patch->delta, &h->hunk, payload); + + if (!line_cb) + continue; + + for (j = 0; !error && j < h->line_count; ++j) { + git_diff_line *l = + git_array_get(patch->lines, h->line_start + j); + + error = line_cb(patch->delta, &h->hunk, l, payload); + } + } + + return error; +} + +size_t git_patch_size( + git_patch *patch, + int include_context, + int include_hunk_headers, + int include_file_headers) +{ + size_t out; + + assert(patch); + + out = patch->content_size; + + if (!include_context) + out -= patch->context_size; + + if (include_hunk_headers) + out += patch->header_size; + + if (include_file_headers) { + git_buf file_header = GIT_BUF_INIT; + + if (git_diff_delta__format_file_header( + &file_header, patch->delta, NULL, NULL, 0) < 0) + giterr_clear(); + else + out += git_buf_len(&file_header); + + git_buf_free(&file_header); + } + + return out; +} + +int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch) +{ + size_t totals[3], idx; + + memset(totals, 0, sizeof(totals)); + + for (idx = 0; idx < git_array_size(patch->lines); ++idx) { + git_diff_line *line = git_array_get(patch->lines, idx); + if (!line) + continue; + + switch (line->origin) { + case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; + case GIT_DIFF_LINE_ADDITION: totals[1]++; break; + case GIT_DIFF_LINE_DELETION: totals[2]++; break; + default: + /* diff --stat and --numstat don't count EOFNL marks because + * they will always be paired with a ADDITION or DELETION line. + */ + break; + } + } + + if (total_ctxt) + *total_ctxt = totals[0]; + if (total_adds) + *total_adds = totals[1]; + if (total_dels) + *total_dels = totals[2]; + + return 0; +} + +const git_diff_delta *git_patch_get_delta(const git_patch *patch) +{ + assert(patch); + return patch->delta; +} + +size_t git_patch_num_hunks(const git_patch *patch) +{ + assert(patch); + return git_array_size(patch->hunks); +} + +static int patch_error_outofrange(const char *thing) +{ + giterr_set(GITERR_INVALID, "patch %s index out of range", thing); + return GIT_ENOTFOUND; +} + +int git_patch_get_hunk( + const git_diff_hunk **out, + size_t *lines_in_hunk, + git_patch *patch, + size_t hunk_idx) +{ + git_patch_hunk *hunk; + assert(patch); + + hunk = git_array_get(patch->hunks, hunk_idx); + + if (!hunk) { + if (out) *out = NULL; + if (lines_in_hunk) *lines_in_hunk = 0; + return patch_error_outofrange("hunk"); + } + + if (out) *out = &hunk->hunk; + if (lines_in_hunk) *lines_in_hunk = hunk->line_count; + return 0; +} + +int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) +{ + git_patch_hunk *hunk; + assert(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) + return patch_error_outofrange("hunk"); + return (int)hunk->line_count; +} + +int git_patch_get_line_in_hunk( + const git_diff_line **out, + git_patch *patch, + size_t hunk_idx, + size_t line_of_hunk) +{ + git_patch_hunk *hunk; + git_diff_line *line; + + assert(patch); + + if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { + if (out) *out = NULL; + return patch_error_outofrange("hunk"); + } + + if (line_of_hunk >= hunk->line_count || + !(line = git_array_get( + patch->lines, hunk->line_start + line_of_hunk))) { + if (out) *out = NULL; + return patch_error_outofrange("line"); + } + + if (out) *out = line; + return 0; +} + +int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx) +{ + assert(out && diff && diff->patch_fn); + return diff->patch_fn(out, diff, idx); +} + +static void git_patch__free(git_patch *patch) +{ + if (patch->free_fn) + patch->free_fn(patch); +} + +void git_patch_free(git_patch *patch) +{ + if (patch) + GIT_REFCOUNT_DEC(patch, git_patch__free); +} diff --git a/src/patch.h b/src/patch.h new file mode 100644 index 000000000..525ae7007 --- /dev/null +++ b/src/patch.h @@ -0,0 +1,66 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ +#ifndef INCLUDE_patch_h__ +#define INCLUDE_patch_h__ + +#include "git2/patch.h" +#include "array.h" + +/* cached information about a hunk in a patch */ +typedef struct git_patch_hunk { + git_diff_hunk hunk; + size_t line_start; + size_t line_count; +} git_patch_hunk; + +struct git_patch { + git_refcount rc; + + git_repository *repo; /* may be null */ + + git_diff_options diff_opts; + + git_diff_delta *delta; + git_diff_binary binary; + git_array_t(git_patch_hunk) hunks; + git_array_t(git_diff_line) lines; + + size_t header_size; + size_t content_size; + size_t context_size; + + void (*free_fn)(git_patch *patch); +}; + +extern int git_patch__invoke_callbacks( + git_patch *patch, + git_diff_file_cb file_cb, + git_diff_binary_cb binary_cb, + git_diff_hunk_cb hunk_cb, + git_diff_line_cb line_cb, + void *payload); + +extern int git_patch_line_stats( + size_t *total_ctxt, + size_t *total_adds, + size_t *total_dels, + const git_patch *patch); + +/** Options for parsing patch files. */ +typedef struct { + /** + * The length of the prefix (in path segments) for the filenames. + * This prefix will be removed when looking for files. The default is 1. + */ + uint32_t prefix_len; +} git_patch_options; + +#define GIT_PATCH_OPTIONS_INIT { 1 } + +extern void git_patch_free(git_patch *patch); + +#endif diff --git a/src/diff_patch.c b/src/patch_generate.c similarity index 61% rename from src/diff_patch.c rename to src/patch_generate.c index 50faa3b3f..0e5d1db31 100644 --- a/src/diff_patch.c +++ b/src/patch_generate.c @@ -7,49 +7,78 @@ #include "common.h" #include "git2/blob.h" #include "diff.h" +#include "diff_generate.h" #include "diff_file.h" #include "diff_driver.h" -#include "diff_patch.h" +#include "patch_generate.h" #include "diff_xdiff.h" #include "delta.h" #include "zstream.h" #include "fileops.h" static void diff_output_init( - git_diff_output*, const git_diff_options*, git_diff_file_cb, + git_patch_generated_output *, const git_diff_options *, git_diff_file_cb, git_diff_binary_cb, git_diff_hunk_cb, git_diff_line_cb, void*); -static void diff_output_to_patch(git_diff_output *, git_patch *); +static void diff_output_to_patch( + git_patch_generated_output *, git_patch_generated *); -static void diff_patch_update_binary(git_patch *patch) +static void patch_generated_free(git_patch *p) { - if ((patch->delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) + git_patch_generated *patch = (git_patch_generated *)p; + + git_array_clear(patch->base.lines); + git_array_clear(patch->base.hunks); + + git__free((char *)patch->base.binary.old_file.data); + git__free((char *)patch->base.binary.new_file.data); + + git_diff_file_content__clear(&patch->ofile); + git_diff_file_content__clear(&patch->nfile); + + git_diff_free(patch->diff); /* decrements refcount */ + patch->diff = NULL; + + git_pool_clear(&patch->flattened); + + git__free((char *)patch->base.diff_opts.old_prefix); + git__free((char *)patch->base.diff_opts.new_prefix); + + if (patch->flags & GIT_PATCH_GENERATED_ALLOCATED) + git__free(patch); +} + +static void patch_generated_update_binary(git_patch_generated *patch) +{ + if ((patch->base.delta->flags & DIFF_FLAGS_KNOWN_BINARY) != 0) return; if ((patch->ofile.file->flags & GIT_DIFF_FLAG_BINARY) != 0 || (patch->nfile.file->flags & GIT_DIFF_FLAG_BINARY) != 0) - patch->delta->flags |= GIT_DIFF_FLAG_BINARY; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; else if (patch->ofile.file->size > GIT_XDIFF_MAX_SIZE || - patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) - patch->delta->flags |= GIT_DIFF_FLAG_BINARY; + patch->nfile.file->size > GIT_XDIFF_MAX_SIZE) + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; else if ((patch->ofile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0 && - (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) - patch->delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; + (patch->nfile.file->flags & DIFF_FLAGS_NOT_BINARY) != 0) + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; } -static void diff_patch_init_common(git_patch *patch) +static void patch_generated_init_common(git_patch_generated *patch) { - diff_patch_update_binary(patch); + patch->base.free_fn = patch_generated_free; - patch->flags |= GIT_DIFF_PATCH_INITIALIZED; + patch_generated_update_binary(patch); + + patch->flags |= GIT_PATCH_GENERATED_INITIALIZED; if (patch->diff) git_diff_addref(patch->diff); } -static int diff_patch_normalize_options( +static int patch_generated_normalize_options( git_diff_options *out, const git_diff_options *opts) { @@ -75,38 +104,40 @@ static int diff_patch_normalize_options( return 0; } -static int diff_patch_init_from_diff( - git_patch *patch, git_diff *diff, size_t delta_index) +static int patch_generated_init( + git_patch_generated *patch, git_diff *diff, size_t delta_index) { int error = 0; memset(patch, 0, sizeof(*patch)); - patch->diff = diff; - patch->delta = git_vector_get(&diff->deltas, delta_index); + + patch->diff = diff; + patch->base.repo = diff->repo; + patch->base.delta = git_vector_get(&diff->deltas, delta_index); patch->delta_index = delta_index; - if ((error = diff_patch_normalize_options( - &patch->diff_opts, &diff->opts)) < 0 || + if ((error = patch_generated_normalize_options( + &patch->base.diff_opts, &diff->opts)) < 0 || (error = git_diff_file_content__init_from_diff( - &patch->ofile, diff, patch->delta, true)) < 0 || + &patch->ofile, diff, patch->base.delta, true)) < 0 || (error = git_diff_file_content__init_from_diff( - &patch->nfile, diff, patch->delta, false)) < 0) + &patch->nfile, diff, patch->base.delta, false)) < 0) return error; - diff_patch_init_common(patch); + patch_generated_init_common(patch); return 0; } -static int diff_patch_alloc_from_diff( - git_patch **out, git_diff *diff, size_t delta_index) +static int patch_generated_alloc_from_diff( + git_patch_generated **out, git_diff *diff, size_t delta_index) { int error; - git_patch *patch = git__calloc(1, sizeof(git_patch)); + git_patch_generated *patch = git__calloc(1, sizeof(git_patch_generated)); GITERR_CHECK_ALLOC(patch); - if (!(error = diff_patch_init_from_diff(patch, diff, delta_index))) { - patch->flags |= GIT_DIFF_PATCH_ALLOCATED; + if (!(error = patch_generated_init(patch, diff, delta_index))) { + patch->flags |= GIT_PATCH_GENERATED_ALLOCATED; GIT_REFCOUNT_INC(patch); } else { git__free(patch); @@ -117,27 +148,27 @@ static int diff_patch_alloc_from_diff( return error; } -GIT_INLINE(bool) should_skip_binary(git_patch *patch, git_diff_file *file) +GIT_INLINE(bool) should_skip_binary(git_patch_generated *patch, git_diff_file *file) { - if ((patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) + if ((patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) != 0) return false; return (file->flags & GIT_DIFF_FLAG_BINARY) != 0; } -static bool diff_patch_diffable(git_patch *patch) +static bool patch_generated_diffable(git_patch_generated *patch) { size_t olen, nlen; - if (patch->delta->status == GIT_DELTA_UNMODIFIED) + if (patch->base.delta->status == GIT_DELTA_UNMODIFIED) return false; /* if we've determined this to be binary (and we are not showing binary * data) then we have skipped loading the map data. instead, query the * file data itself. */ - if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && - (patch->diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0 && + (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) == 0) { olen = (size_t)patch->ofile.file->size; nlen = (size_t)patch->nfile.file->size; } else { @@ -154,12 +185,12 @@ static bool diff_patch_diffable(git_patch *patch) !git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id)); } -static int diff_patch_load(git_patch *patch, git_diff_output *output) +static int patch_generated_load(git_patch_generated *patch, git_patch_generated_output *output) { int error = 0; bool incomplete_data; - if ((patch->flags & GIT_DIFF_PATCH_LOADED) != 0) + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) != 0) return 0; /* if no hunk and data callbacks and user doesn't care if data looks @@ -180,13 +211,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) */ if (patch->ofile.src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load( - &patch->ofile, &patch->diff_opts)) < 0 || + &patch->ofile, &patch->base.diff_opts)) < 0 || should_skip_binary(patch, patch->ofile.file)) goto cleanup; } if (patch->nfile.src == GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load( - &patch->nfile, &patch->diff_opts)) < 0 || + &patch->nfile, &patch->base.diff_opts)) < 0 || should_skip_binary(patch, patch->nfile.file)) goto cleanup; } @@ -194,13 +225,13 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) /* once workdir has been tried, load other data as needed */ if (patch->ofile.src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load( - &patch->ofile, &patch->diff_opts)) < 0 || + &patch->ofile, &patch->base.diff_opts)) < 0 || should_skip_binary(patch, patch->ofile.file)) goto cleanup; } if (patch->nfile.src != GIT_ITERATOR_TYPE_WORKDIR) { if ((error = git_diff_file_content__load( - &patch->nfile, &patch->diff_opts)) < 0 || + &patch->nfile, &patch->base.diff_opts)) < 0 || should_skip_binary(patch, patch->nfile.file)) goto cleanup; } @@ -212,24 +243,24 @@ static int diff_patch_load(git_patch *patch, git_diff_output *output) patch->ofile.file->mode == patch->nfile.file->mode && patch->ofile.file->mode != GIT_FILEMODE_COMMIT && git_oid_equal(&patch->ofile.file->id, &patch->nfile.file->id) && - patch->delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ - patch->delta->status = GIT_DELTA_UNMODIFIED; + patch->base.delta->status == GIT_DELTA_MODIFIED) /* not RENAMED/COPIED! */ + patch->base.delta->status = GIT_DELTA_UNMODIFIED; cleanup: - diff_patch_update_binary(patch); + patch_generated_update_binary(patch); if (!error) { - if (diff_patch_diffable(patch)) - patch->flags |= GIT_DIFF_PATCH_DIFFABLE; + if (patch_generated_diffable(patch)) + patch->flags |= GIT_PATCH_GENERATED_DIFFABLE; - patch->flags |= GIT_DIFF_PATCH_LOADED; + patch->flags |= GIT_PATCH_GENERATED_LOADED; } return error; } -static int diff_patch_invoke_file_callback( - git_patch *patch, git_diff_output *output) +static int patch_generated_invoke_file_callback( + git_patch_generated *patch, git_patch_generated_output *output) { float progress = patch->diff ? ((float)patch->delta_index / patch->diff->deltas.length) : 1.0f; @@ -238,7 +269,7 @@ static int diff_patch_invoke_file_callback( return 0; return giterr_set_after_callback_function( - output->file_cb(patch->delta, progress, output->payload), + output->file_cb(patch->base.delta, progress, output->payload), "git_patch"); } @@ -253,7 +284,7 @@ static int create_binary( size_t b_datalen) { git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT; - unsigned long delta_data_len; + size_t delta_data_len = 0; int error; /* The git_delta function accepts unsigned long only */ @@ -270,20 +301,24 @@ static int create_binary( } if (a_datalen && b_datalen) { - void *delta_data = git_delta( - a_data, (unsigned long)a_datalen, - b_data, (unsigned long)b_datalen, - &delta_data_len, (unsigned long)deflate.size); + void *delta_data; - if (delta_data) { + error = git_delta(&delta_data, &delta_data_len, + a_data, a_datalen, + b_data, b_datalen, + deflate.size); + + if (error == 0) { error = git_zstream_deflatebuf( - &delta, delta_data, (size_t)delta_data_len); + &delta, delta_data, delta_data_len); git__free(delta_data); - - if (error < 0) - goto done; + } else if (error == GIT_EBUFS) { + error = 0; } + + if (error < 0) + goto done; } if (delta.size && delta.size < deflate.size) { @@ -305,32 +340,38 @@ done: return error; } -static int diff_binary(git_diff_output *output, git_patch *patch) +static int diff_binary(git_patch_generated_output *output, git_patch_generated *patch) { - git_diff_binary binary = {{0}}; + git_diff_binary binary = {0}; const char *old_data = patch->ofile.map.data; const char *new_data = patch->nfile.map.data; size_t old_len = patch->ofile.map.len, new_len = patch->nfile.map.len; int error; - /* Create the old->new delta (as the "new" side of the patch), - * and the new->old delta (as the "old" side) - */ - if ((error = create_binary(&binary.old_file.type, - (char **)&binary.old_file.data, - &binary.old_file.datalen, - &binary.old_file.inflatedlen, - new_data, new_len, old_data, old_len)) < 0 || - (error = create_binary(&binary.new_file.type, - (char **)&binary.new_file.data, - &binary.new_file.datalen, - &binary.new_file.inflatedlen, - old_data, old_len, new_data, new_len)) < 0) - return error; + /* Only load contents if the user actually wants to diff + * binary files. */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) { + binary.contains_data = 1; + + /* Create the old->new delta (as the "new" side of the patch), + * and the new->old delta (as the "old" side) + */ + if ((error = create_binary(&binary.old_file.type, + (char **)&binary.old_file.data, + &binary.old_file.datalen, + &binary.old_file.inflatedlen, + new_data, new_len, old_data, old_len)) < 0 || + (error = create_binary(&binary.new_file.type, + (char **)&binary.new_file.data, + &binary.new_file.datalen, + &binary.new_file.inflatedlen, + old_data, old_len, new_data, new_len)) < 0) + return error; + } error = giterr_set_after_callback_function( - output->binary_cb(patch->delta, &binary, output->payload), + output->binary_cb(patch->base.delta, &binary, output->payload), "git_patch"); git__free((char *) binary.old_file.data); @@ -339,25 +380,27 @@ static int diff_binary(git_diff_output *output, git_patch *patch) return error; } -static int diff_patch_generate(git_patch *patch, git_diff_output *output) +static int patch_generated_create( + git_patch_generated *patch, + git_patch_generated_output *output) { int error = 0; - if ((patch->flags & GIT_DIFF_PATCH_DIFFED) != 0) + if ((patch->flags & GIT_PATCH_GENERATED_DIFFED) != 0) return 0; /* if we are not looking at the binary or text data, don't do the diff */ if (!output->binary_cb && !output->hunk_cb && !output->data_cb) return 0; - if ((patch->flags & GIT_DIFF_PATCH_LOADED) == 0 && - (error = diff_patch_load(patch, output)) < 0) + if ((patch->flags & GIT_PATCH_GENERATED_LOADED) == 0 && + (error = patch_generated_load(patch, output)) < 0) return error; - if ((patch->flags & GIT_DIFF_PATCH_DIFFABLE) == 0) + if ((patch->flags & GIT_PATCH_GENERATED_DIFFABLE) == 0) return 0; - if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { + if ((patch->base.delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { if (output->binary_cb) error = diff_binary(output, patch); } @@ -366,33 +409,10 @@ static int diff_patch_generate(git_patch *patch, git_diff_output *output) error = output->diff_cb(output, patch); } - patch->flags |= GIT_DIFF_PATCH_DIFFED; + patch->flags |= GIT_PATCH_GENERATED_DIFFED; return error; } -static void diff_patch_free(git_patch *patch) -{ - git_diff_file_content__clear(&patch->ofile); - git_diff_file_content__clear(&patch->nfile); - - git_array_clear(patch->lines); - git_array_clear(patch->hunks); - - git_diff_free(patch->diff); /* decrements refcount */ - patch->diff = NULL; - - git_pool_clear(&patch->flattened); - - git__free((char *)patch->diff_opts.old_prefix); - git__free((char *)patch->diff_opts.new_prefix); - - git__free((char *)patch->binary.old_file.data); - git__free((char *)patch->binary.new_file.data); - - if (patch->flags & GIT_DIFF_PATCH_ALLOCATED) - git__free(patch); -} - static int diff_required(git_diff *diff, const char *action) { if (diff) @@ -412,7 +432,7 @@ int git_diff_foreach( int error = 0; git_xdiff_output xo; size_t idx; - git_patch patch; + git_patch_generated patch; if ((error = diff_required(diff, "git_diff_foreach")) < 0) return error; @@ -423,24 +443,24 @@ int git_diff_foreach( &xo.output, &diff->opts, file_cb, binary_cb, hunk_cb, data_cb, payload); git_xdiff_init(&xo, &diff->opts); - git_vector_foreach(&diff->deltas, idx, patch.delta) { + git_vector_foreach(&diff->deltas, idx, patch.base.delta) { /* check flags against patch status */ - if (git_diff_delta__should_skip(&diff->opts, patch.delta)) + if (git_diff_delta__should_skip(&diff->opts, patch.base.delta)) continue; if (binary_cb || hunk_cb || data_cb) { - if ((error = diff_patch_init_from_diff(&patch, diff, idx)) != 0 || - (error = diff_patch_load(&patch, &xo.output)) != 0) + if ((error = patch_generated_init(&patch, diff, idx)) != 0 || + (error = patch_generated_load(&patch, &xo.output)) != 0) return error; } - if ((error = diff_patch_invoke_file_callback(&patch, &xo.output)) == 0) { + if ((error = patch_generated_invoke_file_callback(&patch, &xo.output)) == 0) { if (binary_cb || hunk_cb || data_cb) - error = diff_patch_generate(&patch, &xo.output); + error = patch_generated_create(&patch, &xo.output); } - git_patch_free(&patch); + git_patch_free(&patch.base); if (error) break; @@ -450,15 +470,15 @@ int git_diff_foreach( } typedef struct { - git_patch patch; + git_patch_generated patch; git_diff_delta delta; char paths[GIT_FLEX_ARRAY]; -} diff_patch_with_delta; +} patch_generated_with_delta; -static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) +static int diff_single_generate(patch_generated_with_delta *pd, git_xdiff_output *xo) { int error = 0; - git_patch *patch = &pd->patch; + git_patch_generated *patch = &pd->patch; bool has_old = ((patch->ofile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); bool has_new = ((patch->nfile.flags & GIT_DIFF_FLAG__NO_DATA) == 0); @@ -469,24 +489,33 @@ static int diff_single_generate(diff_patch_with_delta *pd, git_xdiff_output *xo) if (git_oid_equal(&patch->nfile.file->id, &patch->ofile.file->id)) pd->delta.status = GIT_DELTA_UNMODIFIED; - patch->delta = &pd->delta; + patch->base.delta = &pd->delta; - diff_patch_init_common(patch); + patch_generated_init_common(patch); if (pd->delta.status == GIT_DELTA_UNMODIFIED && - !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) - return error; + !(patch->ofile.opts_flags & GIT_DIFF_INCLUDE_UNMODIFIED)) { - error = diff_patch_invoke_file_callback(patch, (git_diff_output *)xo); + /* Even empty patches are flagged as binary, and even though + * there's no difference, we flag this as "containing data" + * (the data is known to be empty, as opposed to wholly unknown). + */ + if (patch->base.diff_opts.flags & GIT_DIFF_SHOW_BINARY) + patch->base.binary.contains_data = 1; + + return error; + } + + error = patch_generated_invoke_file_callback(patch, (git_patch_generated_output *)xo); if (!error) - error = diff_patch_generate(patch, (git_diff_output *)xo); + error = patch_generated_create(patch, (git_patch_generated_output *)xo); return error; } -static int diff_patch_from_sources( - diff_patch_with_delta *pd, +static int patch_generated_from_sources( + patch_generated_with_delta *pd, git_xdiff_output *xo, git_diff_file_content_src *oldsrc, git_diff_file_content_src *newsrc, @@ -499,7 +528,7 @@ static int diff_patch_from_sources( git_diff_file *lfile = &pd->delta.old_file, *rfile = &pd->delta.new_file; git_diff_file_content *ldata = &pd->patch.ofile, *rdata = &pd->patch.nfile; - if ((error = diff_patch_normalize_options(&pd->patch.diff_opts, opts)) < 0) + if ((error = patch_generated_normalize_options(&pd->patch.base.diff_opts, opts)) < 0) return error; if (opts && (opts->flags & GIT_DIFF_REVERSE) != 0) { @@ -507,7 +536,7 @@ static int diff_patch_from_sources( tmp = ldata; ldata = rdata; rdata = tmp; } - pd->patch.delta = &pd->delta; + pd->patch.base.delta = &pd->delta; if (!oldsrc->as_path) { if (newsrc->as_path) @@ -530,12 +559,12 @@ static int diff_patch_from_sources( return diff_single_generate(pd, xo); } -static int diff_patch_with_delta_alloc( - diff_patch_with_delta **out, +static int patch_generated_with_delta_alloc( + patch_generated_with_delta **out, const char **old_path, const char **new_path) { - diff_patch_with_delta *pd; + patch_generated_with_delta *pd; size_t old_len = *old_path ? strlen(*old_path) : 0; size_t new_len = *new_path ? strlen(*new_path) : 0; size_t alloc_len; @@ -547,7 +576,7 @@ static int diff_patch_with_delta_alloc( *out = pd = git__calloc(1, alloc_len); GITERR_CHECK_ALLOC(pd); - pd->patch.flags = GIT_DIFF_PATCH_ALLOCATED; + pd->patch.flags = GIT_PATCH_GENERATED_ALLOCATED; if (*old_path) { memcpy(&pd->paths[0], *old_path, old_len); @@ -575,7 +604,7 @@ static int diff_from_sources( void *payload) { int error = 0; - diff_patch_with_delta pd; + patch_generated_with_delta pd; git_xdiff_output xo; memset(&xo, 0, sizeof(xo)); @@ -585,9 +614,9 @@ static int diff_from_sources( memset(&pd, 0, sizeof(pd)); - error = diff_patch_from_sources(&pd, &xo, oldsrc, newsrc, opts); + error = patch_generated_from_sources(&pd, &xo, oldsrc, newsrc, opts); - git_patch_free(&pd.patch); + git_patch_free(&pd.patch.base); return error; } @@ -599,13 +628,13 @@ static int patch_from_sources( const git_diff_options *opts) { int error = 0; - diff_patch_with_delta *pd; + patch_generated_with_delta *pd; git_xdiff_output xo; assert(out); *out = NULL; - if ((error = diff_patch_with_delta_alloc( + if ((error = patch_generated_with_delta_alloc( &pd, &oldsrc->as_path, &newsrc->as_path)) < 0) return error; @@ -613,7 +642,7 @@ static int patch_from_sources( diff_output_to_patch(&xo.output, &pd->patch); git_xdiff_init(&xo, opts); - if (!(error = diff_patch_from_sources(pd, &xo, oldsrc, newsrc, opts))) + if (!(error = patch_generated_from_sources(pd, &xo, oldsrc, newsrc, opts))) *out = (git_patch *)pd; else git_patch_free((git_patch *)pd); @@ -732,13 +761,13 @@ int git_patch_from_buffers( return patch_from_sources(out, &osrc, &nsrc, opts); } -int git_patch_from_diff( +int git_patch_generated_from_diff( git_patch **patch_ptr, git_diff *diff, size_t idx) { int error = 0; git_xdiff_output xo; git_diff_delta *delta = NULL; - git_patch *patch = NULL; + git_patch_generated *patch = NULL; if (patch_ptr) *patch_ptr = NULL; @@ -760,17 +789,17 @@ int git_patch_from_diff( (diff->opts.flags & GIT_DIFF_SKIP_BINARY_CHECK) != 0)) return 0; - if ((error = diff_patch_alloc_from_diff(&patch, diff, idx)) < 0) + if ((error = patch_generated_alloc_from_diff(&patch, diff, idx)) < 0) return error; memset(&xo, 0, sizeof(xo)); diff_output_to_patch(&xo.output, patch); git_xdiff_init(&xo, &diff->opts); - error = diff_patch_invoke_file_callback(patch, &xo.output); + error = patch_generated_invoke_file_callback(patch, &xo.output); if (!error) - error = diff_patch_generate(patch, &xo.output); + error = patch_generated_create(patch, &xo.output); if (!error) { /* TODO: if cumulative diff size is < 0.5 total size, flatten patch */ @@ -778,237 +807,34 @@ int git_patch_from_diff( } if (error || !patch_ptr) - git_patch_free(patch); + git_patch_free(&patch->base); else - *patch_ptr = patch; + *patch_ptr = &patch->base; return error; } -void git_patch_free(git_patch *patch) -{ - if (patch) - GIT_REFCOUNT_DEC(patch, diff_patch_free); -} - -const git_diff_delta *git_patch_get_delta(const git_patch *patch) -{ - assert(patch); - return patch->delta; -} - -size_t git_patch_num_hunks(const git_patch *patch) -{ - assert(patch); - return git_array_size(patch->hunks); -} - -int git_patch_line_stats( - size_t *total_ctxt, - size_t *total_adds, - size_t *total_dels, - const git_patch *patch) -{ - size_t totals[3], idx; - - memset(totals, 0, sizeof(totals)); - - for (idx = 0; idx < git_array_size(patch->lines); ++idx) { - git_diff_line *line = git_array_get(patch->lines, idx); - if (!line) - continue; - - switch (line->origin) { - case GIT_DIFF_LINE_CONTEXT: totals[0]++; break; - case GIT_DIFF_LINE_ADDITION: totals[1]++; break; - case GIT_DIFF_LINE_DELETION: totals[2]++; break; - default: - /* diff --stat and --numstat don't count EOFNL marks because - * they will always be paired with a ADDITION or DELETION line. - */ - break; - } - } - - if (total_ctxt) - *total_ctxt = totals[0]; - if (total_adds) - *total_adds = totals[1]; - if (total_dels) - *total_dels = totals[2]; - - return 0; -} - -static int diff_error_outofrange(const char *thing) -{ - giterr_set(GITERR_INVALID, "Diff patch %s index out of range", thing); - return GIT_ENOTFOUND; -} - -int git_patch_get_hunk( - const git_diff_hunk **out, - size_t *lines_in_hunk, - git_patch *patch, - size_t hunk_idx) -{ - diff_patch_hunk *hunk; - assert(patch); - - hunk = git_array_get(patch->hunks, hunk_idx); - - if (!hunk) { - if (out) *out = NULL; - if (lines_in_hunk) *lines_in_hunk = 0; - return diff_error_outofrange("hunk"); - } - - if (out) *out = &hunk->hunk; - if (lines_in_hunk) *lines_in_hunk = hunk->line_count; - return 0; -} - -int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx) -{ - diff_patch_hunk *hunk; - assert(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) - return diff_error_outofrange("hunk"); - return (int)hunk->line_count; -} - -int git_patch_get_line_in_hunk( - const git_diff_line **out, - git_patch *patch, - size_t hunk_idx, - size_t line_of_hunk) -{ - diff_patch_hunk *hunk; - git_diff_line *line; - - assert(patch); - - if (!(hunk = git_array_get(patch->hunks, hunk_idx))) { - if (out) *out = NULL; - return diff_error_outofrange("hunk"); - } - - if (line_of_hunk >= hunk->line_count || - !(line = git_array_get( - patch->lines, hunk->line_start + line_of_hunk))) { - if (out) *out = NULL; - return diff_error_outofrange("line"); - } - - if (out) *out = line; - return 0; -} - -size_t git_patch_size( - git_patch *patch, - int include_context, - int include_hunk_headers, - int include_file_headers) -{ - size_t out; - - assert(patch); - - out = patch->content_size; - - if (!include_context) - out -= patch->context_size; - - if (include_hunk_headers) - out += patch->header_size; - - if (include_file_headers) { - git_buf file_header = GIT_BUF_INIT; - - if (git_diff_delta__format_file_header( - &file_header, patch->delta, NULL, NULL, 0) < 0) - giterr_clear(); - else - out += git_buf_len(&file_header); - - git_buf_free(&file_header); - } - - return out; -} - -git_diff *git_patch__diff(git_patch *patch) -{ - return patch->diff; -} - -git_diff_driver *git_patch__driver(git_patch *patch) +git_diff_driver *git_patch_generated_driver(git_patch_generated *patch) { /* ofile driver is representative for whole patch */ return patch->ofile.driver; } -void git_patch__old_data( - char **ptr, size_t *len, git_patch *patch) +void git_patch_generated_old_data( + char **ptr, size_t *len, git_patch_generated *patch) { *ptr = patch->ofile.map.data; *len = patch->ofile.map.len; } -void git_patch__new_data( - char **ptr, size_t *len, git_patch *patch) +void git_patch_generated_new_data( + char **ptr, size_t *len, git_patch_generated *patch) { *ptr = patch->nfile.map.data; *len = patch->nfile.map.len; } -int git_patch__invoke_callbacks( - git_patch *patch, - git_diff_file_cb file_cb, - git_diff_binary_cb binary_cb, - git_diff_hunk_cb hunk_cb, - git_diff_line_cb line_cb, - void *payload) -{ - int error = 0; - uint32_t i, j; - - if (file_cb) - error = file_cb(patch->delta, 0, payload); - - if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) { - if (binary_cb) - error = binary_cb(patch->delta, &patch->binary, payload); - - return error; - } - - if (!hunk_cb && !line_cb) - return error; - - for (i = 0; !error && i < git_array_size(patch->hunks); ++i) { - diff_patch_hunk *h = git_array_get(patch->hunks, i); - - if (hunk_cb) - error = hunk_cb(patch->delta, &h->hunk, payload); - - if (!line_cb) - continue; - - for (j = 0; !error && j < h->line_count; ++j) { - git_diff_line *l = - git_array_get(patch->lines, h->line_start + j); - - error = line_cb(patch->delta, &h->hunk, l, payload); - } - } - - return error; -} - - -static int diff_patch_file_cb( +static int patch_generated_file_cb( const git_diff_delta *delta, float progress, void *payload) @@ -1017,7 +843,7 @@ static int diff_patch_file_cb( return 0; } -static int diff_patch_binary_cb( +static int patch_generated_binary_cb( const git_diff_delta *delta, const git_diff_binary *binary, void *payload) @@ -1047,62 +873,62 @@ static int diff_patch_binary_cb( return 0; } -static int diff_patch_hunk_cb( +static int git_patch_hunk_cb( const git_diff_delta *delta, const git_diff_hunk *hunk_, void *payload) { - git_patch *patch = payload; - diff_patch_hunk *hunk; + git_patch_generated *patch = payload; + git_patch_hunk *hunk; GIT_UNUSED(delta); - hunk = git_array_alloc(patch->hunks); + hunk = git_array_alloc(patch->base.hunks); GITERR_CHECK_ALLOC(hunk); memcpy(&hunk->hunk, hunk_, sizeof(hunk->hunk)); - patch->header_size += hunk_->header_len; + patch->base.header_size += hunk_->header_len; - hunk->line_start = git_array_size(patch->lines); + hunk->line_start = git_array_size(patch->base.lines); hunk->line_count = 0; return 0; } -static int diff_patch_line_cb( +static int patch_generated_line_cb( const git_diff_delta *delta, const git_diff_hunk *hunk_, const git_diff_line *line_, void *payload) { - git_patch *patch = payload; - diff_patch_hunk *hunk; + git_patch_generated *patch = payload; + git_patch_hunk *hunk; git_diff_line *line; GIT_UNUSED(delta); GIT_UNUSED(hunk_); - hunk = git_array_last(patch->hunks); + hunk = git_array_last(patch->base.hunks); assert(hunk); /* programmer error if no hunk is available */ - line = git_array_alloc(patch->lines); + line = git_array_alloc(patch->base.lines); GITERR_CHECK_ALLOC(line); memcpy(line, line_, sizeof(*line)); /* do some bookkeeping so we can provide old/new line numbers */ - patch->content_size += line->content_len; + patch->base.content_size += line->content_len; if (line->origin == GIT_DIFF_LINE_ADDITION || line->origin == GIT_DIFF_LINE_DELETION) - patch->content_size += 1; + patch->base.content_size += 1; else if (line->origin == GIT_DIFF_LINE_CONTEXT) { - patch->content_size += 1; - patch->context_size += line->content_len + 1; + patch->base.content_size += 1; + patch->base.context_size += line->content_len + 1; } else if (line->origin == GIT_DIFF_LINE_CONTEXT_EOFNL) - patch->context_size += line->content_len; + patch->base.context_size += line->content_len; hunk->line_count++; @@ -1110,7 +936,7 @@ static int diff_patch_line_cb( } static void diff_output_init( - git_diff_output *out, + git_patch_generated_output *out, const git_diff_options *opts, git_diff_file_cb file_cb, git_diff_binary_cb binary_cb, @@ -1129,14 +955,15 @@ static void diff_output_init( out->payload = payload; } -static void diff_output_to_patch(git_diff_output *out, git_patch *patch) +static void diff_output_to_patch( + git_patch_generated_output *out, git_patch_generated *patch) { diff_output_init( out, NULL, - diff_patch_file_cb, - diff_patch_binary_cb, - diff_patch_hunk_cb, - diff_patch_line_cb, + patch_generated_file_cb, + patch_generated_binary_cb, + git_patch_hunk_cb, + patch_generated_line_cb, patch); } diff --git a/src/patch_generate.h b/src/patch_generate.h new file mode 100644 index 000000000..2e89b5c4d --- /dev/null +++ b/src/patch_generate.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_patch_generate_h__ +#define INCLUDE_patch_generate_h__ + +#include "common.h" +#include "diff.h" +#include "diff_file.h" +#include "patch.h" + +enum { + GIT_PATCH_GENERATED_ALLOCATED = (1 << 0), + GIT_PATCH_GENERATED_INITIALIZED = (1 << 1), + GIT_PATCH_GENERATED_LOADED = (1 << 2), + /* the two sides are different */ + GIT_PATCH_GENERATED_DIFFABLE = (1 << 3), + /* the difference between the two sides has been computed */ + GIT_PATCH_GENERATED_DIFFED = (1 << 4), + GIT_PATCH_GENERATED_FLATTENED = (1 << 5), +}; + +struct git_patch_generated { + struct git_patch base; + + git_diff *diff; /* for refcount purposes, maybe NULL for blob diffs */ + size_t delta_index; + git_diff_file_content ofile; + git_diff_file_content nfile; + uint32_t flags; + git_pool flattened; +}; + +typedef struct git_patch_generated git_patch_generated; + +extern git_diff_driver *git_patch_generated_driver(git_patch_generated *); + +extern void git_patch_generated_old_data( + char **, size_t *, git_patch_generated *); +extern void git_patch_generated_new_data( + char **, size_t *, git_patch_generated *); +extern int git_patch_generated_from_diff( + git_patch **, git_diff *, size_t); + +typedef struct git_patch_generated_output git_patch_generated_output; + +struct git_patch_generated_output { + /* these callbacks are issued with the diff data */ + git_diff_file_cb file_cb; + git_diff_binary_cb binary_cb; + git_diff_hunk_cb hunk_cb; + git_diff_line_cb data_cb; + void *payload; + + /* this records the actual error in cases where it may be obscured */ + int error; + + /* this callback is used to do the diff and drive the other callbacks. + * see diff_xdiff.h for how to use this in practice for now. + */ + int (*diff_cb)(git_patch_generated_output *output, + git_patch_generated *patch); +}; + +#endif diff --git a/src/patch_parse.c b/src/patch_parse.c new file mode 100644 index 000000000..f5275947d --- /dev/null +++ b/src/patch_parse.c @@ -0,0 +1,1159 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#include "git2/patch.h" +#include "patch.h" +#include "patch_parse.h" +#include "diff_parse.h" +#include "path.h" + +#define parse_err(...) \ + ( giterr_set(GITERR_PATCH, __VA_ARGS__), -1 ) + +typedef struct { + git_patch base; + + git_patch_parse_ctx *ctx; + + /* the paths from the `diff --git` header, these will be used if this is not + * a rename (and rename paths are specified) or if no `+++`/`---` line specify + * the paths. + */ + char *header_old_path, *header_new_path; + + /* renamed paths are precise and are not prefixed */ + char *rename_old_path, *rename_new_path; + + /* the paths given in `---` and `+++` lines */ + char *old_path, *new_path; + + /* the prefixes from the old/new paths */ + char *old_prefix, *new_prefix; +} git_patch_parsed; + + +GIT_INLINE(bool) parse_ctx_contains( + git_patch_parse_ctx *ctx, const char *str, size_t len) +{ + return (ctx->line_len >= len && memcmp(ctx->line, str, len) == 0); +} + +#define parse_ctx_contains_s(ctx, str) \ + parse_ctx_contains(ctx, str, sizeof(str) - 1) + +static void parse_advance_line(git_patch_parse_ctx *ctx) +{ + ctx->line += ctx->line_len; + ctx->remain_len -= ctx->line_len; + ctx->line_len = git__linenlen(ctx->line, ctx->remain_len); + ctx->line_num++; +} + +static void parse_advance_chars(git_patch_parse_ctx *ctx, size_t char_cnt) +{ + ctx->line += char_cnt; + ctx->remain_len -= char_cnt; + ctx->line_len -= char_cnt; +} + +static int parse_advance_expected( + git_patch_parse_ctx *ctx, + const char *expected, + size_t expected_len) +{ + if (ctx->line_len < expected_len) + return -1; + + if (memcmp(ctx->line, expected, expected_len) != 0) + return -1; + + parse_advance_chars(ctx, expected_len); + return 0; +} + +#define parse_advance_expected_str(ctx, str) \ + parse_advance_expected(ctx, str, strlen(str)) + +static int parse_advance_ws(git_patch_parse_ctx *ctx) +{ + int ret = -1; + + while (ctx->line_len > 0 && + ctx->line[0] != '\n' && + git__isspace(ctx->line[0])) { + ctx->line++; + ctx->line_len--; + ctx->remain_len--; + ret = 0; + } + + return ret; +} + +static int parse_advance_nl(git_patch_parse_ctx *ctx) +{ + if (ctx->line_len != 1 || ctx->line[0] != '\n') + return -1; + + parse_advance_line(ctx); + return 0; +} + +static int header_path_len(git_patch_parse_ctx *ctx) +{ + bool inquote = 0; + bool quoted = (ctx->line_len > 0 && ctx->line[0] == '"'); + size_t len; + + for (len = quoted; len < ctx->line_len; len++) { + if (!quoted && git__isspace(ctx->line[len])) + break; + else if (quoted && !inquote && ctx->line[len] == '"') { + len++; + break; + } + + inquote = (!inquote && ctx->line[len] == '\\'); + } + + return len; +} + +static int parse_header_path_buf(git_buf *path, git_patch_parse_ctx *ctx) +{ + int path_len, error = 0; + + path_len = header_path_len(ctx); + + if ((error = git_buf_put(path, ctx->line, path_len)) < 0) + goto done; + + parse_advance_chars(ctx, path_len); + + git_buf_rtrim(path); + + if (path->size > 0 && path->ptr[0] == '"') + error = git_buf_unquote(path); + + if (error < 0) + goto done; + + git_path_squash_slashes(path); + +done: + return error; +} + +static int parse_header_path(char **out, git_patch_parse_ctx *ctx) +{ + git_buf path = GIT_BUF_INIT; + int error = parse_header_path_buf(&path, ctx); + + *out = git_buf_detach(&path); + + return error; +} + +static int parse_header_git_oldpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_path(&patch->old_path, ctx); +} + +static int parse_header_git_newpath( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_path(&patch->new_path, ctx); +} + +static int parse_header_mode(uint16_t *mode, git_patch_parse_ctx *ctx) +{ + const char *end; + int32_t m; + int ret; + + if (ctx->line_len < 1 || !git__isdigit(ctx->line[0])) + return parse_err("invalid file mode at line %"PRIuZ, ctx->line_num); + + if ((ret = git__strntol32(&m, ctx->line, ctx->line_len, &end, 8)) < 0) + return ret; + + if (m > UINT16_MAX) + return -1; + + *mode = (uint16_t)m; + + parse_advance_chars(ctx, (end - ctx->line)); + + return ret; +} + +static int parse_header_oid( + git_oid *oid, + uint16_t *oid_len, + git_patch_parse_ctx *ctx) +{ + size_t len; + + for (len = 0; len < ctx->line_len && len < GIT_OID_HEXSZ; len++) { + if (!git__isxdigit(ctx->line[len])) + break; + } + + if (len < GIT_OID_MINPREFIXLEN || len > GIT_OID_HEXSZ || + git_oid_fromstrn(oid, ctx->line, len) < 0) + return parse_err("invalid hex formatted object id at line %"PRIuZ, + ctx->line_num); + + parse_advance_chars(ctx, len); + + *oid_len = (uint16_t)len; + + return 0; +} + +static int parse_header_git_index( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_oid(&patch->base.delta->old_file.id, + &patch->base.delta->old_file.id_abbrev, ctx) < 0 || + parse_advance_expected_str(ctx, "..") < 0 || + parse_header_oid(&patch->base.delta->new_file.id, + &patch->base.delta->new_file.id_abbrev, ctx) < 0) + return -1; + + if (ctx->line_len > 0 && ctx->line[0] == ' ') { + uint16_t mode; + + parse_advance_chars(ctx, 1); + + if (parse_header_mode(&mode, ctx) < 0) + return -1; + + if (!patch->base.delta->new_file.mode) + patch->base.delta->new_file.mode = mode; + + if (!patch->base.delta->old_file.mode) + patch->base.delta->old_file.mode = mode; + } + + return 0; +} + +static int parse_header_git_oldmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newmode( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_git_deletedfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->old_file.path); + + patch->base.delta->old_file.path = NULL; + patch->base.delta->status = GIT_DELTA_DELETED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->old_file.mode, ctx); +} + +static int parse_header_git_newfilemode( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git__free((char *)patch->base.delta->new_file.path); + + patch->base.delta->new_file.path = NULL; + patch->base.delta->status = GIT_DELTA_ADDED; + patch->base.delta->nfiles = 1; + + return parse_header_mode(&patch->base.delta->new_file.mode, ctx); +} + +static int parse_header_rename( + char **out, + git_patch_parse_ctx *ctx) +{ + git_buf path = GIT_BUF_INIT; + + if (parse_header_path_buf(&path, ctx) < 0) + return -1; + + /* Note: the `rename from` and `rename to` lines include the literal + * filename. They do *not* include the prefix. (Who needs consistency?) + */ + *out = git_buf_detach(&path); + return 0; +} + +static int parse_header_renamefrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_renameto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_RENAMED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_copyfrom( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_old_path, ctx); +} + +static int parse_header_copyto( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + patch->base.delta->status = GIT_DELTA_COPIED; + return parse_header_rename(&patch->rename_new_path, ctx); +} + +static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx) +{ + int32_t val; + const char *end; + + if (ctx->line_len < 1 || !git__isdigit(ctx->line[0]) || + git__strntol32(&val, ctx->line, ctx->line_len, &end, 10) < 0) + return -1; + + parse_advance_chars(ctx, (end - ctx->line)); + + if (parse_advance_expected_str(ctx, "%") < 0) + return -1; + + if (val > 100) + return -1; + + *out = val; + return 0; +} + +static int parse_header_similarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_header_percent(&patch->base.delta->similarity, ctx) < 0) + return parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->line_num); + + return 0; +} + +static int parse_header_dissimilarity( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + uint16_t dissimilarity; + + if (parse_header_percent(&dissimilarity, ctx) < 0) + return parse_err("invalid similarity percentage at line %"PRIuZ, + ctx->line_num); + + patch->base.delta->similarity = 100 - dissimilarity; + + return 0; +} + +typedef struct { + const char *str; + int(*fn)(git_patch_parsed *, git_patch_parse_ctx *); +} header_git_op; + +static const header_git_op header_git_ops[] = { + { "diff --git ", NULL }, + { "@@ -", NULL }, + { "GIT binary patch", NULL }, + { "Binary files ", NULL }, + { "--- ", parse_header_git_oldpath }, + { "+++ ", parse_header_git_newpath }, + { "index ", parse_header_git_index }, + { "old mode ", parse_header_git_oldmode }, + { "new mode ", parse_header_git_newmode }, + { "deleted file mode ", parse_header_git_deletedfilemode }, + { "new file mode ", parse_header_git_newfilemode }, + { "rename from ", parse_header_renamefrom }, + { "rename to ", parse_header_renameto }, + { "rename old ", parse_header_renamefrom }, + { "rename new ", parse_header_renameto }, + { "copy from ", parse_header_copyfrom }, + { "copy to ", parse_header_copyto }, + { "similarity index ", parse_header_similarity }, + { "dissimilarity index ", parse_header_dissimilarity }, +}; + +static int parse_header_git( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + size_t i; + int error = 0; + + /* Parse the diff --git line */ + if (parse_advance_expected_str(ctx, "diff --git ") < 0) + return parse_err("corrupt git diff header at line %"PRIuZ, ctx->line_num); + + if (parse_header_path(&patch->header_old_path, ctx) < 0) + return parse_err("corrupt old path in git diff header at line %"PRIuZ, + ctx->line_num); + + if (parse_advance_ws(ctx) < 0 || + parse_header_path(&patch->header_new_path, ctx) < 0) + return parse_err("corrupt new path in git diff header at line %"PRIuZ, + ctx->line_num); + + /* Parse remaining header lines */ + for (parse_advance_line(ctx); + ctx->remain_len > 0; + parse_advance_line(ctx)) { + + bool found = false; + + if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') + break; + + for (i = 0; i < ARRAY_SIZE(header_git_ops); i++) { + const header_git_op *op = &header_git_ops[i]; + size_t op_len = strlen(op->str); + + if (memcmp(ctx->line, op->str, min(op_len, ctx->line_len)) != 0) + continue; + + /* Do not advance if this is the patch separator */ + if (op->fn == NULL) + goto done; + + parse_advance_chars(ctx, op_len); + + if ((error = op->fn(patch, ctx)) < 0) + goto done; + + parse_advance_ws(ctx); + parse_advance_expected_str(ctx, "\n"); + + if (ctx->line_len > 0) { + error = parse_err("trailing data at line %"PRIuZ, ctx->line_num); + goto done; + } + + found = true; + break; + } + + if (!found) { + error = parse_err("invalid patch header at line %"PRIuZ, + ctx->line_num); + goto done; + } + } + +done: + return error; +} + +static int parse_number(git_off_t *out, git_patch_parse_ctx *ctx) +{ + const char *end; + int64_t num; + + if (!git__isdigit(ctx->line[0])) + return -1; + + if (git__strntol64(&num, ctx->line, ctx->line_len, &end, 10) < 0) + return -1; + + if (num < 0) + return -1; + + *out = num; + parse_advance_chars(ctx, (end - ctx->line)); + + return 0; +} + +static int parse_int(int *out, git_patch_parse_ctx *ctx) +{ + git_off_t num; + + if (parse_number(&num, ctx) < 0 || !git__is_int(num)) + return -1; + + *out = (int)num; + return 0; +} + +static int parse_hunk_header( + git_patch_hunk *hunk, + git_patch_parse_ctx *ctx) +{ + const char *header_start = ctx->line; + + hunk->hunk.old_lines = 1; + hunk->hunk.new_lines = 1; + + if (parse_advance_expected_str(ctx, "@@ -") < 0 || + parse_int(&hunk->hunk.old_start, ctx) < 0) + goto fail; + + if (ctx->line_len > 0 && ctx->line[0] == ',') { + if (parse_advance_expected_str(ctx, ",") < 0 || + parse_int(&hunk->hunk.old_lines, ctx) < 0) + goto fail; + } + + if (parse_advance_expected_str(ctx, " +") < 0 || + parse_int(&hunk->hunk.new_start, ctx) < 0) + goto fail; + + if (ctx->line_len > 0 && ctx->line[0] == ',') { + if (parse_advance_expected_str(ctx, ",") < 0 || + parse_int(&hunk->hunk.new_lines, ctx) < 0) + goto fail; + } + + if (parse_advance_expected_str(ctx, " @@") < 0) + goto fail; + + parse_advance_line(ctx); + + if (!hunk->hunk.old_lines && !hunk->hunk.new_lines) + goto fail; + + hunk->hunk.header_len = ctx->line - header_start; + if (hunk->hunk.header_len > (GIT_DIFF_HUNK_HEADER_SIZE - 1)) + return parse_err("oversized patch hunk header at line %"PRIuZ, + ctx->line_num); + + memcpy(hunk->hunk.header, header_start, hunk->hunk.header_len); + hunk->hunk.header[hunk->hunk.header_len] = '\0'; + + return 0; + +fail: + giterr_set(GITERR_PATCH, "invalid patch hunk header at line %"PRIuZ, + ctx->line_num); + return -1; +} + +static int parse_hunk_body( + git_patch_parsed *patch, + git_patch_hunk *hunk, + git_patch_parse_ctx *ctx) +{ + git_diff_line *line; + int error = 0; + + int oldlines = hunk->hunk.old_lines; + int newlines = hunk->hunk.new_lines; + + for (; + ctx->remain_len > 4 && (oldlines || newlines) && + memcmp(ctx->line, "@@ -", 4) != 0; + parse_advance_line(ctx)) { + + int origin; + int prefix = 1; + + if (ctx->line_len == 0 || ctx->line[ctx->line_len - 1] != '\n') { + error = parse_err("invalid patch instruction at line %"PRIuZ, + ctx->line_num); + goto done; + } + + switch (ctx->line[0]) { + case '\n': + prefix = 0; + + case ' ': + origin = GIT_DIFF_LINE_CONTEXT; + oldlines--; + newlines--; + break; + + case '-': + origin = GIT_DIFF_LINE_DELETION; + oldlines--; + break; + + case '+': + origin = GIT_DIFF_LINE_ADDITION; + newlines--; + break; + + default: + error = parse_err("invalid patch hunk at line %"PRIuZ, ctx->line_num); + goto done; + } + + line = git_array_alloc(patch->base.lines); + GITERR_CHECK_ALLOC(line); + + memset(line, 0x0, sizeof(git_diff_line)); + + line->content = ctx->line + prefix; + line->content_len = ctx->line_len - prefix; + line->content_offset = ctx->content_len - ctx->remain_len; + line->origin = origin; + + hunk->line_count++; + } + + if (oldlines || newlines) { + error = parse_err( + "invalid patch hunk, expected %d old lines and %d new lines", + hunk->hunk.old_lines, hunk->hunk.new_lines); + goto done; + } + + /* Handle "\ No newline at end of file". Only expect the leading + * backslash, though, because the rest of the string could be + * localized. Because `diff` optimizes for the case where you + * want to apply the patch by hand. + */ + if (parse_ctx_contains_s(ctx, "\\ ") && + git_array_size(patch->base.lines) > 0) { + + line = git_array_get(patch->base.lines, git_array_size(patch->base.lines) - 1); + + if (line->content_len < 1) { + error = parse_err("cannot trim trailing newline of empty line"); + goto done; + } + + line->content_len--; + + parse_advance_line(ctx); + } + +done: + return error; +} + +static int parse_patch_header( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error = 0; + + for (ctx->line = ctx->remain; + ctx->remain_len > 0; + parse_advance_line(ctx)) { + + /* This line is too short to be a patch header. */ + if (ctx->line_len < 6) + continue; + + /* This might be a hunk header without a patch header, provide a + * sensible error message. */ + if (parse_ctx_contains_s(ctx, "@@ -")) { + size_t line_num = ctx->line_num; + git_patch_hunk hunk; + + /* If this cannot be parsed as a hunk header, it's just leading + * noise, continue. + */ + if (parse_hunk_header(&hunk, ctx) < 0) { + giterr_clear(); + continue; + } + + error = parse_err("invalid hunk header outside patch at line %"PRIuZ, + line_num); + goto done; + } + + /* This buffer is too short to contain a patch. */ + if (ctx->remain_len < ctx->line_len + 6) + break; + + /* A proper git patch */ + if (parse_ctx_contains_s(ctx, "diff --git ")) { + error = parse_header_git(patch, ctx); + goto done; + } + + error = 0; + continue; + } + + giterr_set(GITERR_PATCH, "no patch found"); + error = GIT_ENOTFOUND; + +done: + return error; +} + +static int parse_patch_binary_side( + git_diff_binary_file *binary, + git_patch_parse_ctx *ctx) +{ + git_diff_binary_t type = GIT_DIFF_BINARY_NONE; + git_buf base85 = GIT_BUF_INIT, decoded = GIT_BUF_INIT; + git_off_t len; + int error = 0; + + if (parse_ctx_contains_s(ctx, "literal ")) { + type = GIT_DIFF_BINARY_LITERAL; + parse_advance_chars(ctx, 8); + } else if (parse_ctx_contains_s(ctx, "delta ")) { + type = GIT_DIFF_BINARY_DELTA; + parse_advance_chars(ctx, 6); + } else { + error = parse_err( + "unknown binary delta type at line %"PRIuZ, ctx->line_num); + goto done; + } + + if (parse_number(&len, ctx) < 0 || parse_advance_nl(ctx) < 0 || len < 0) { + error = parse_err("invalid binary size at line %"PRIuZ, ctx->line_num); + goto done; + } + + while (ctx->line_len) { + char c = ctx->line[0]; + size_t encoded_len, decoded_len = 0, decoded_orig = decoded.size; + + if (c == '\n') + break; + else if (c >= 'A' && c <= 'Z') + decoded_len = c - 'A' + 1; + else if (c >= 'a' && c <= 'z') + decoded_len = c - 'a' + (('z' - 'a') + 1) + 1; + + if (!decoded_len) { + error = parse_err("invalid binary length at line %"PRIuZ, ctx->line_num); + goto done; + } + + parse_advance_chars(ctx, 1); + + encoded_len = ((decoded_len / 4) + !!(decoded_len % 4)) * 5; + + if (encoded_len > ctx->line_len - 1) { + error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num); + goto done; + } + + if ((error = git_buf_decode_base85( + &decoded, ctx->line, encoded_len, decoded_len)) < 0) + goto done; + + if (decoded.size - decoded_orig != decoded_len) { + error = parse_err("truncated binary data at line %"PRIuZ, ctx->line_num); + goto done; + } + + parse_advance_chars(ctx, encoded_len); + + if (parse_advance_nl(ctx) < 0) { + error = parse_err("trailing data at line %"PRIuZ, ctx->line_num); + goto done; + } + } + + binary->type = type; + binary->inflatedlen = (size_t)len; + binary->datalen = decoded.size; + binary->data = git_buf_detach(&decoded); + +done: + git_buf_free(&base85); + git_buf_free(&decoded); + return error; +} + +static int parse_patch_binary( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + int error; + + if (parse_advance_expected_str(ctx, "GIT binary patch") < 0 || + parse_advance_nl(ctx) < 0) + return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num); + + /* parse old->new binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.new_file, ctx)) < 0) + return error; + + if (parse_advance_nl(ctx) < 0) + return parse_err("corrupt git binary separator at line %"PRIuZ, + ctx->line_num); + + /* parse new->old binary diff */ + if ((error = parse_patch_binary_side( + &patch->base.binary.old_file, ctx)) < 0) + return error; + + if (parse_advance_nl(ctx) < 0) + return parse_err("corrupt git binary patch separator at line %"PRIuZ, + ctx->line_num); + + patch->base.binary.contains_data = 1; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +static int parse_patch_binary_nodata( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + if (parse_advance_expected_str(ctx, "Binary files ") < 0 || + parse_advance_expected_str(ctx, patch->header_old_path) < 0 || + parse_advance_expected_str(ctx, " and ") < 0 || + parse_advance_expected_str(ctx, patch->header_new_path) < 0 || + parse_advance_expected_str(ctx, " differ") < 0 || + parse_advance_nl(ctx) < 0) + return parse_err("corrupt git binary header at line %"PRIuZ, ctx->line_num); + + patch->base.binary.contains_data = 0; + patch->base.delta->flags |= GIT_DIFF_FLAG_BINARY; + return 0; +} + +static int parse_patch_hunks( + git_patch_parsed *patch, + git_patch_parse_ctx *ctx) +{ + git_patch_hunk *hunk; + int error = 0; + + while (parse_ctx_contains_s(ctx, "@@ -")) { + hunk = git_array_alloc(patch->base.hunks); + GITERR_CHECK_ALLOC(hunk); + + memset(hunk, 0, sizeof(git_patch_hunk)); + + hunk->line_start = git_array_size(patch->base.lines); + hunk->line_count = 0; + + if ((error = parse_hunk_header(hunk, ctx)) < 0 || + (error = parse_hunk_body(patch, hunk, ctx)) < 0) + goto done; + } + + patch->base.delta->flags |= GIT_DIFF_FLAG_NOT_BINARY; + +done: + return error; +} + +static int parse_patch_body( + git_patch_parsed *patch, git_patch_parse_ctx *ctx) +{ + if (parse_ctx_contains_s(ctx, "GIT binary patch")) + return parse_patch_binary(patch, ctx); + else if (parse_ctx_contains_s(ctx, "Binary files ")) + return parse_patch_binary_nodata(patch, ctx); + else + return parse_patch_hunks(patch, ctx); +} + +int check_header_names( + const char *one, + const char *two, + const char *old_or_new, + bool two_null) +{ + if (!one || !two) + return 0; + + if (two_null && strcmp(two, "/dev/null") != 0) + return parse_err("expected %s path of '/dev/null'", old_or_new); + + else if (!two_null && strcmp(one, two) != 0) + return parse_err("mismatched %s path names", old_or_new); + + return 0; +} + +static int check_prefix( + char **out, + size_t *out_len, + git_patch_parsed *patch, + const char *path_start) +{ + const char *path = path_start; + size_t prefix_len = patch->ctx->opts.prefix_len; + size_t remain_len = prefix_len; + + *out = NULL; + *out_len = 0; + + if (prefix_len == 0) + goto done; + + /* leading slashes do not count as part of the prefix in git apply */ + while (*path == '/') + path++; + + while (*path && remain_len) { + if (*path == '/') + remain_len--; + + path++; + } + + if (remain_len || !*path) + return parse_err( + "header filename does not contain %"PRIuZ" path components", + prefix_len); + +done: + *out_len = (path - path_start); + *out = git__strndup(path_start, *out_len); + + return (*out == NULL) ? -1 : 0; +} + +static int check_filenames(git_patch_parsed *patch) +{ + const char *prefixed_new, *prefixed_old; + size_t old_prefixlen = 0, new_prefixlen = 0; + bool added = (patch->base.delta->status == GIT_DELTA_ADDED); + bool deleted = (patch->base.delta->status == GIT_DELTA_DELETED); + + if (patch->old_path && !patch->new_path) + return parse_err("missing new path"); + + if (!patch->old_path && patch->new_path) + return parse_err("missing old path"); + + /* Ensure (non-renamed) paths match */ + if (check_header_names( + patch->header_old_path, patch->old_path, "old", added) < 0 || + check_header_names( + patch->header_new_path, patch->new_path, "new", deleted) < 0) + return -1; + + prefixed_old = (!added && patch->old_path) ? patch->old_path : + patch->header_old_path; + prefixed_new = (!deleted && patch->new_path) ? patch->new_path : + patch->header_new_path; + + if (check_prefix( + &patch->old_prefix, &old_prefixlen, patch, prefixed_old) < 0 || + check_prefix( + &patch->new_prefix, &new_prefixlen, patch, prefixed_new) < 0) + return -1; + + /* Prefer the rename filenames as they are unambiguous and unprefixed */ + if (patch->rename_old_path) + patch->base.delta->old_file.path = patch->rename_old_path; + else + patch->base.delta->old_file.path = prefixed_old + old_prefixlen; + + if (patch->rename_new_path) + patch->base.delta->new_file.path = patch->rename_new_path; + else + patch->base.delta->new_file.path = prefixed_new + new_prefixlen; + + if (!patch->base.delta->old_file.path && + !patch->base.delta->new_file.path) + return parse_err("git diff header lacks old / new paths"); + + return 0; +} + +static int check_patch(git_patch_parsed *patch) +{ + git_diff_delta *delta = patch->base.delta; + + if (check_filenames(patch) < 0) + return -1; + + if (delta->old_file.path && + delta->status != GIT_DELTA_DELETED && + !delta->new_file.mode) + delta->new_file.mode = delta->old_file.mode; + + if (delta->status == GIT_DELTA_MODIFIED && + !(delta->flags & GIT_DIFF_FLAG_BINARY) && + delta->new_file.mode == delta->old_file.mode && + git_array_size(patch->base.hunks) == 0) + return parse_err("patch with no hunks"); + + if (delta->status == GIT_DELTA_ADDED) { + memset(&delta->old_file.id, 0x0, sizeof(git_oid)); + delta->old_file.id_abbrev = 0; + } + + if (delta->status == GIT_DELTA_DELETED) { + memset(&delta->new_file.id, 0x0, sizeof(git_oid)); + delta->new_file.id_abbrev = 0; + } + + return 0; +} + +git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + git_patch_options default_opts = GIT_PATCH_OPTIONS_INIT; + + if ((ctx = git__calloc(1, sizeof(git_patch_parse_ctx))) == NULL) + return NULL; + + if (content_len) { + if ((ctx->content = git__malloc(content_len)) == NULL) { + git__free(ctx); + return NULL; + } + + memcpy((char *)ctx->content, content, content_len); + } + + ctx->content_len = content_len; + ctx->remain = ctx->content; + ctx->remain_len = ctx->content_len; + + if (opts) + memcpy(&ctx->opts, opts, sizeof(git_patch_options)); + else + memcpy(&ctx->opts, &default_opts, sizeof(git_patch_options)); + + GIT_REFCOUNT_INC(ctx); + return ctx; +} + +static void patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + if (!ctx) + return; + + git__free((char *)ctx->content); + git__free(ctx); +} + +void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx) +{ + GIT_REFCOUNT_DEC(ctx, patch_parse_ctx_free); +} + +int git_patch_parsed_from_diff(git_patch **out, git_diff *d, size_t idx) +{ + git_diff_parsed *diff = (git_diff_parsed *)d; + git_patch *p; + + if ((p = git_vector_get(&diff->patches, idx)) == NULL) + return -1; + + GIT_REFCOUNT_INC(p); + *out = p; + + return 0; +} + +static void patch_parsed__free(git_patch *p) +{ + git_patch_parsed *patch = (git_patch_parsed *)p; + + if (!patch) + return; + + git_patch_parse_ctx_free(patch->ctx); + + git__free((char *)patch->base.binary.old_file.data); + git__free((char *)patch->base.binary.new_file.data); + git_array_clear(patch->base.hunks); + git_array_clear(patch->base.lines); + git__free(patch->base.delta); + + git__free(patch->old_prefix); + git__free(patch->new_prefix); + git__free(patch->header_old_path); + git__free(patch->header_new_path); + git__free(patch->rename_old_path); + git__free(patch->rename_new_path); + git__free(patch->old_path); + git__free(patch->new_path); + git__free(patch); +} + +int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx) +{ + git_patch_parsed *patch; + size_t start, used; + int error = 0; + + assert(out && ctx); + + *out = NULL; + + patch = git__calloc(1, sizeof(git_patch_parsed)); + GITERR_CHECK_ALLOC(patch); + + patch->ctx = ctx; + GIT_REFCOUNT_INC(patch->ctx); + + patch->base.free_fn = patch_parsed__free; + + patch->base.delta = git__calloc(1, sizeof(git_diff_delta)); + GITERR_CHECK_ALLOC(patch->base.delta); + + patch->base.delta->status = GIT_DELTA_MODIFIED; + patch->base.delta->nfiles = 2; + + start = ctx->remain_len; + + if ((error = parse_patch_header(patch, ctx)) < 0 || + (error = parse_patch_body(patch, ctx)) < 0 || + (error = check_patch(patch)) < 0) + goto done; + + used = start - ctx->remain_len; + ctx->remain += used; + + patch->base.diff_opts.old_prefix = patch->old_prefix; + patch->base.diff_opts.new_prefix = patch->new_prefix; + patch->base.diff_opts.flags |= GIT_DIFF_SHOW_BINARY; + + GIT_REFCOUNT_INC(patch); + *out = &patch->base; + +done: + if (error < 0) + patch_parsed__free(&patch->base); + + return error; +} + +int git_patch_from_buffer( + git_patch **out, + const char *content, + size_t content_len, + const git_patch_options *opts) +{ + git_patch_parse_ctx *ctx; + int error; + + ctx = git_patch_parse_ctx_init(content, content_len, opts); + GITERR_CHECK_ALLOC(ctx); + + error = git_patch_parse(out, ctx); + + git_patch_parse_ctx_free(ctx); + return error; +} + diff --git a/src/patch_parse.h b/src/patch_parse.h new file mode 100644 index 000000000..99eb4587b --- /dev/null +++ b/src/patch_parse.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_patch_parse_h__ +#define INCLUDE_patch_parse_h__ + +typedef struct { + git_refcount rc; + + /* Original content buffer */ + const char *content; + size_t content_len; + + git_patch_options opts; + + /* The remaining (unparsed) buffer */ + const char *remain; + size_t remain_len; + + const char *line; + size_t line_len; + size_t line_num; +} git_patch_parse_ctx; + +extern git_patch_parse_ctx *git_patch_parse_ctx_init( + const char *content, + size_t content_len, + const git_patch_options *opts); + +extern void git_patch_parse_ctx_free(git_patch_parse_ctx *ctx); + +/** + * Create a patch for a single file from the contents of a patch buffer. + * + * @param out The patch to be created + * @param contents The contents of a patch file + * @param contents_len The length of the patch file + * @param opts The git_patch_options + * @return 0 on success, <0 on failure. + */ +extern int git_patch_from_buffer( + git_patch **out, + const char *contents, + size_t contents_len, + const git_patch_options *opts); + +extern int git_patch_parse( + git_patch **out, + git_patch_parse_ctx *ctx); + +extern int git_patch_parsed_from_diff(git_patch **, git_diff *, size_t); + +#endif diff --git a/src/path.c b/src/path.c index 1fd14fcb9..767552778 100644 --- a/src/path.c +++ b/src/path.c @@ -306,6 +306,25 @@ int git_path_join_unrooted( return 0; } +void git_path_squash_slashes(git_buf *path) +{ + char *p, *q; + + if (path->size == 0) + return; + + for (p = path->ptr, q = path->ptr; *q; p++, q++) { + *p = *q; + + while (*q == '/' && *(q+1) == '/') { + path->size--; + q++; + } + } + + *p = '\0'; +} + int git_path_prettify(git_buf *path_out, const char *path, const char *base) { char buf[GIT_PATH_MAX]; @@ -625,6 +644,10 @@ int git_path_set_error(int errno_value, const char *path, const char *action) giterr_set(GITERR_OS, "Failed %s - '%s' already exists", action, path); return GIT_EEXISTS; + case EACCES: + giterr_set(GITERR_OS, "Failed %s - '%s' is locked", action, path); + return GIT_ELOCKED; + default: giterr_set(GITERR_OS, "Could not %s '%s'", action, path); return -1; @@ -810,6 +833,20 @@ int git_path_cmp( return (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; } +size_t git_path_common_dirlen(const char *one, const char *two) +{ + const char *p, *q, *dirsep = NULL; + + for (p = one, q = two; *p && *q; p++, q++) { + if (*p == '/' && *q == '/') + dirsep = p; + else if (*p != *q) + break; + } + + return dirsep ? (dirsep - one) + 1 : 0; +} + int git_path_make_relative(git_buf *path, const char *parent) { const char *p, *q, *p_dirsep, *q_dirsep; @@ -1108,7 +1145,6 @@ int git_path_diriter_init( unsigned int flags) { git_win32_path path_filter; - git_buf hack = {0}; static int is_win7_or_later = -1; if (is_win7_or_later < 0) @@ -1314,7 +1350,7 @@ int git_path_diriter_next(git_path_diriter *diriter) return GIT_ITEROVER; giterr_set(GITERR_OS, - "Could not read directory '%s'", diriter->path); + "Could not read directory '%s'", diriter->path.ptr); return -1; } } while (skip_dot && git_path_is_dot_or_dotdot(de->d_name)); diff --git a/src/path.h b/src/path.h index 875c8cb7e..fb45a6534 100644 --- a/src/path.h +++ b/src/path.h @@ -202,6 +202,18 @@ extern bool git_path_contains(git_buf *dir, const char *item); */ extern bool git_path_contains_dir(git_buf *parent, const char *subdir); +/** + * Determine the common directory length between two paths, including + * the final path separator. For example, given paths 'a/b/c/1.txt + * and 'a/b/c/d/2.txt', the common directory is 'a/b/c/', and this + * will return the length of the string 'a/b/c/', which is 6. + * + * @param one The first path + * @param two The second path + * @return The length of the common directory + */ +extern size_t git_path_common_dirlen(const char *one, const char *two); + /** * Make the path relative to the given parent path. * @@ -231,6 +243,12 @@ extern bool git_path_contains_file(git_buf *dir, const char *file); extern int git_path_join_unrooted( git_buf *path_out, const char *path, const char *base, ssize_t *root_at); +/** + * Removes multiple occurrences of '/' in a row, squashing them into a + * single '/'. + */ +extern void git_path_squash_slashes(git_buf *path); + /** * Clean up path, prepending base if it is not already rooted. */ diff --git a/src/pathspec.c b/src/pathspec.c index 8a93cdd50..361b398b8 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -418,7 +418,7 @@ static int pathspec_match_from_iterator( GITERR_CHECK_ALLOC(m); } - if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) + if ((error = git_iterator_reset_range(iter, ps->prefix, ps->prefix)) < 0) goto done; if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && diff --git a/src/pool.h b/src/pool.h index e0fafa997..f61f16944 100644 --- a/src/pool.h +++ b/src/pool.h @@ -35,6 +35,8 @@ typedef struct { uint32_t page_size; /* size of page in bytes */ } git_pool; +#define GIT_POOL_INIT { NULL, 0, 0 } + #else /** @@ -57,6 +59,9 @@ typedef struct { uint32_t item_size; uint32_t page_size; } git_pool; + +#define GIT_POOL_INIT { GIT_VECTOR_INIT, 0, 0 } + #endif /** diff --git a/src/pqueue.c b/src/pqueue.c index 54a60ca04..9341d1af3 100644 --- a/src/pqueue.c +++ b/src/pqueue.c @@ -86,14 +86,15 @@ int git_pqueue_insert(git_pqueue *pq, void *item) if ((pq->flags & GIT_PQUEUE_FIXED_SIZE) != 0 && pq->length >= pq->_alloc_size) { - /* skip this item if below min item in heap */ - if (pq->_cmp(item, git_vector_get(pq, 0)) <= 0) + /* skip this item if below min item in heap or if + * we do not have a comparison function */ + if (!pq->_cmp || pq->_cmp(item, git_vector_get(pq, 0)) <= 0) return 0; /* otherwise remove the min item before inserting new */ (void)git_pqueue_pop(pq); } - if (!(error = git_vector_insert(pq, item))) + if (!(error = git_vector_insert(pq, item)) && pq->_cmp) pqueue_up(pq, pq->length - 1); return error; @@ -101,9 +102,15 @@ int git_pqueue_insert(git_pqueue *pq, void *item) void *git_pqueue_pop(git_pqueue *pq) { - void *rval = git_pqueue_get(pq, 0); + void *rval; - if (git_pqueue_size(pq) > 1) { + if (!pq->_cmp) { + rval = git_vector_last(pq); + } else { + rval = git_pqueue_get(pq, 0); + } + + if (git_pqueue_size(pq) > 1 && pq->_cmp) { /* move last item to top of heap, shrink, and push item down */ pq->contents[0] = git_vector_last(pq); git_vector_pop(pq); diff --git a/src/pqueue.h b/src/pqueue.h index da7b74edf..76b14919e 100644 --- a/src/pqueue.h +++ b/src/pqueue.h @@ -35,6 +35,7 @@ extern int git_pqueue_init( #define git_pqueue_clear git_vector_clear #define git_pqueue_size git_vector_length #define git_pqueue_get git_vector_get +#define git_pqueue_reverse git_vector_reverse /** * Insert a new item into the queue diff --git a/src/proxy.c b/src/proxy.c new file mode 100644 index 000000000..f53ac1151 --- /dev/null +++ b/src/proxy.c @@ -0,0 +1,32 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "git2/proxy.h" + +int git_proxy_init_options(git_proxy_options *opts, unsigned int version) +{ + GIT_INIT_STRUCTURE_FROM_TEMPLATE( + opts, version, git_proxy_options, GIT_PROXY_OPTIONS_INIT); + return 0; +} + +int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src) +{ + if (!src) { + git_proxy_init_options(tgt, GIT_PROXY_OPTIONS_VERSION); + return 0; + } + + memcpy(tgt, src, sizeof(git_proxy_options)); + if (src->url) { + tgt->url = git__strdup(src->url); + GITERR_CHECK_ALLOC(tgt->url); + } + + return 0; +} diff --git a/src/proxy.h b/src/proxy.h new file mode 100644 index 000000000..bf9382737 --- /dev/null +++ b/src/proxy.h @@ -0,0 +1,14 @@ +/* +* Copyright (C) the libgit2 contributors. All rights reserved. +* +* This file is part of libgit2, distributed under the GNU GPL v2 with +* a Linking Exception. For full terms see the included COPYING file. +*/ +#ifndef INCLUDE_proxy_h__ +#define INCLUDE_proxy_h__ + +#include "git2/proxy.h" + +extern int git_proxy_options_dup(git_proxy_options *tgt, const git_proxy_options *src); + +#endif \ No newline at end of file diff --git a/src/push.c b/src/push.c index 0747259c8..b4901388b 100644 --- a/src/push.c +++ b/src/push.c @@ -639,7 +639,7 @@ int git_push_finish(git_push *push, const git_remote_callbacks *callbacks) int error; if (!git_remote_connected(push->remote) && - (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks, push->custom_headers)) < 0) + (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks, NULL, push->custom_headers)) < 0) return error; if ((error = filter_refs(push->remote)) < 0 || diff --git a/src/rebase.c b/src/rebase.c index bcad9b7cd..0af2b3cf4 100644 --- a/src/rebase.c +++ b/src/rebase.c @@ -472,6 +472,7 @@ done: static int rebase_setupfiles(git_rebase *rebase) { char onto[GIT_OID_HEXSZ], orig_head[GIT_OID_HEXSZ]; + const char *orig_head_name; git_oid_fmt(onto, &rebase->onto_id); git_oid_fmt(orig_head, &rebase->orig_head_id); @@ -481,8 +482,11 @@ static int rebase_setupfiles(git_rebase *rebase) return -1; } + orig_head_name = rebase->head_detached ? ORIG_DETACHED_HEAD : + rebase->orig_head_name; + if (git_repository__set_orig_head(rebase->repo, &rebase->orig_head_id) < 0 || - rebase_setupfile(rebase, HEAD_NAME_FILE, -1, "%s\n", rebase->orig_head_name) < 0 || + rebase_setupfile(rebase, HEAD_NAME_FILE, -1, "%s\n", orig_head_name) < 0 || rebase_setupfile(rebase, ONTO_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, onto) < 0 || rebase_setupfile(rebase, ORIG_HEAD_FILE, -1, "%.*s\n", GIT_OID_HEXSZ, orig_head) < 0 || rebase_setupfile(rebase, QUIET_FILE, -1, rebase->quiet ? "t\n" : "\n") < 0) @@ -582,7 +586,7 @@ static int rebase_init_operations( (error = git_revwalk_hide(revwalk, git_annotated_commit_id(upstream))) < 0) goto done; - git_revwalk_sorting(revwalk, GIT_SORT_REVERSE | GIT_SORT_TIME); + git_revwalk_sorting(revwalk, GIT_SORT_REVERSE); while ((error = git_revwalk_next(&id, revwalk)) == 0) { if ((error = git_commit_lookup(&commit, repo, &id)) < 0) @@ -626,8 +630,12 @@ static int rebase_init_merge( rebase->state_path = git_buf_detach(&state_path); GITERR_CHECK_ALLOC(rebase->state_path); - rebase->orig_head_name = git__strdup(branch->ref_name ? branch->ref_name : ORIG_DETACHED_HEAD); - GITERR_CHECK_ALLOC(rebase->orig_head_name); + if (branch->ref_name && strcmp(branch->ref_name, "HEAD")) { + rebase->orig_head_name = git__strdup(branch->ref_name); + GITERR_CHECK_ALLOC(rebase->orig_head_name); + } else { + rebase->head_detached = 1; + } rebase->onto_name = git__strdup(rebase_onto_name(onto)); GITERR_CHECK_ALLOC(rebase->onto_name); @@ -844,6 +852,7 @@ static int rebase_next_inmemory( git_tree *current_tree = NULL, *head_tree = NULL, *parent_tree = NULL; git_rebase_operation *operation; git_index *index = NULL; + unsigned int parent_count; int error; *out = NULL; @@ -851,10 +860,20 @@ static int rebase_next_inmemory( operation = git_array_get(rebase->operations, rebase->current); if ((error = git_commit_lookup(¤t_commit, rebase->repo, &operation->id)) < 0 || - (error = git_commit_tree(¤t_tree, current_commit)) < 0 || - (error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || - (error = git_commit_tree(&parent_tree, parent_commit)) < 0 || - (error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || + (error = git_commit_tree(¤t_tree, current_commit)) < 0) + goto done; + + if ((parent_count = git_commit_parentcount(current_commit)) > 1) { + giterr_set(GITERR_REBASE, "Cannot rebase a merge commit"); + error = -1; + goto done; + } else if (parent_count) { + if ((error = git_commit_parent(&parent_commit, current_commit, 0)) < 0 || + (error = git_commit_tree(&parent_tree, parent_commit)) < 0) + goto done; + } + + if ((error = git_commit_tree(&head_tree, rebase->last_commit)) < 0 || (error = git_merge_trees(&index, rebase->repo, parent_tree, head_tree, current_tree, &rebase->options.merge_options)) < 0) goto done; @@ -1028,15 +1047,12 @@ static int rebase_commit_inmemory( const char *message_encoding, const char *message) { - git_rebase_operation *operation; git_commit *commit = NULL; int error = 0; - operation = git_array_get(rebase->operations, rebase->current); - - assert(operation); assert(rebase->index); assert(rebase->last_commit); + assert(rebase->current < rebase->operations.size); if ((error = rebase_commit__create(&commit, rebase, rebase->index, rebase->last_commit, author, committer, message_encoding, message)) < 0) @@ -1254,42 +1270,33 @@ done: return error; } -int git_rebase_finish( - git_rebase *rebase, - const git_signature *signature) +static int return_to_orig_head(git_rebase *rebase) { git_reference *terminal_ref = NULL, *branch_ref = NULL, *head_ref = NULL; git_commit *terminal_commit = NULL; git_buf branch_msg = GIT_BUF_INIT, head_msg = GIT_BUF_INIT; char onto[GIT_OID_HEXSZ]; - int error; - - assert(rebase); - - if (rebase->inmemory) - return 0; + int error = 0; git_oid_fmt(onto, &rebase->onto_id); - if ((error = git_buf_printf(&branch_msg, "rebase finished: %s onto %.*s", - rebase->orig_head_name, GIT_OID_HEXSZ, onto)) < 0 || - (error = git_buf_printf(&head_msg, "rebase finished: returning to %s", - rebase->orig_head_name)) < 0 || - (error = git_repository_head(&terminal_ref, rebase->repo)) < 0 || + if ((error = git_buf_printf(&branch_msg, + "rebase finished: %s onto %.*s", + rebase->orig_head_name, GIT_OID_HEXSZ, onto)) == 0 && + (error = git_buf_printf(&head_msg, + "rebase finished: returning to %s", + rebase->orig_head_name)) == 0 && + (error = git_repository_head(&terminal_ref, rebase->repo)) == 0 && (error = git_reference_peel((git_object **)&terminal_commit, - terminal_ref, GIT_OBJ_COMMIT)) < 0 || + terminal_ref, GIT_OBJ_COMMIT)) == 0 && (error = git_reference_create_matching(&branch_ref, - rebase->repo, rebase->orig_head_name, git_commit_id(terminal_commit), 1, - &rebase->orig_head_id, branch_msg.ptr)) < 0 || - (error = git_reference_symbolic_create(&head_ref, + rebase->repo, rebase->orig_head_name, + git_commit_id(terminal_commit), 1, + &rebase->orig_head_id, branch_msg.ptr)) == 0) + error = git_reference_symbolic_create(&head_ref, rebase->repo, GIT_HEAD_FILE, rebase->orig_head_name, 1, - head_msg.ptr)) < 0 || - (error = rebase_copy_notes(rebase, signature)) < 0) - goto done; + head_msg.ptr); - error = rebase_cleanup(rebase); - -done: git_buf_free(&head_msg); git_buf_free(&branch_msg); git_commit_free(terminal_commit); @@ -1300,6 +1307,26 @@ done: return error; } +int git_rebase_finish( + git_rebase *rebase, + const git_signature *signature) +{ + int error = 0; + + assert(rebase); + + if (rebase->inmemory) + return 0; + + if (!rebase->head_detached) + error = return_to_orig_head(rebase); + + if (error == 0 && (error = rebase_copy_notes(rebase, signature)) == 0) + error = rebase_cleanup(rebase); + + return error; +} + size_t git_rebase_operation_entrycount(git_rebase *rebase) { assert(rebase); diff --git a/src/refdb.c b/src/refdb.c index debba1276..85c84892b 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -125,13 +125,15 @@ int git_refdb_lookup(git_reference **out, git_refdb *db, const char *ref_name) int git_refdb_iterator(git_reference_iterator **out, git_refdb *db, const char *glob) { + int error; + if (!db->backend || !db->backend->iterator) { giterr_set(GITERR_REFERENCE, "This backend doesn't support iterators"); return -1; } - if (db->backend->iterator(out, db->backend, glob) < 0) - return -1; + if ((error = db->backend->iterator(out, db->backend, glob)) < 0) + return error; GIT_REFCOUNT_INC(db); (*out)->db = db; diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f978038e6..8739d5b89 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -326,12 +326,13 @@ static int refdb_fs_backend__exists( { refdb_fs_backend *backend = (refdb_fs_backend *)_backend; git_buf ref_path = GIT_BUF_INIT; + int error; assert(backend); - if (packed_reload(backend) < 0 || - git_buf_joinpath(&ref_path, backend->path, ref_name) < 0) - return -1; + if ((error = packed_reload(backend)) < 0 || + (error = git_buf_joinpath(&ref_path, backend->path, ref_name)) < 0) + return error; *exists = git_path_isfile(ref_path.ptr) || (git_sortedcache_lookup(backend->refcache, ref_name) != NULL); @@ -409,8 +410,8 @@ static int packed_lookup( int error = 0; struct packref *entry; - if (packed_reload(backend) < 0) - return -1; + if ((error = packed_reload(backend)) < 0) + return error; if (git_sortedcache_rlock(backend->refcache) < 0) return -1; @@ -615,13 +616,14 @@ static int refdb_fs_backend__iterator_next_name( static int refdb_fs_backend__iterator( git_reference_iterator **out, git_refdb_backend *_backend, const char *glob) { + int error; refdb_fs_iter *iter; refdb_fs_backend *backend = (refdb_fs_backend *)_backend; assert(backend); - if (packed_reload(backend) < 0) - return -1; + if ((error = packed_reload(backend)) < 0) + return error; iter = git__calloc(1, sizeof(refdb_fs_iter)); GITERR_CHECK_ALLOC(iter); @@ -674,16 +676,18 @@ static int reference_path_available( int force) { size_t i; + int error; - if (packed_reload(backend) < 0) - return -1; + if ((error = packed_reload(backend)) < 0) + return error; if (!force) { int exists; - if (refdb_fs_backend__exists( - &exists, (git_refdb_backend *)backend, new_ref) < 0) - return -1; + if ((error = refdb_fs_backend__exists( + &exists, (git_refdb_backend *)backend, new_ref)) < 0) { + return error; + } if (exists) { giterr_set(GITERR_REFERENCE, @@ -725,8 +729,8 @@ static int loose_lock(git_filebuf *file, refdb_fs_backend *backend, const char * /* Remove a possibly existing empty directory hierarchy * which name would collide with the reference name */ - if (git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY) < 0) - return -1; + if ((error = git_futils_rmdir_r(name, backend->path, GIT_RMDIR_SKIP_NONEMPTY)) < 0) + return error; if (git_buf_joinpath(&ref_path, backend->path, name) < 0) return -1; @@ -901,40 +905,62 @@ static int packed_write_ref(struct packref *ref, git_filebuf *file) static int packed_remove_loose(refdb_fs_backend *backend) { size_t i; - git_buf full_path = GIT_BUF_INIT; - int failed = 0; + git_filebuf lock = GIT_FILEBUF_INIT; + git_buf ref_content = GIT_BUF_INIT; + int error = 0; /* backend->refcache is already locked when this is called */ for (i = 0; i < git_sortedcache_entrycount(backend->refcache); ++i) { struct packref *ref = git_sortedcache_entry(backend->refcache, i); + git_oid current_id; if (!ref || !(ref->flags & PACKREF_WAS_LOOSE)) continue; - if (git_buf_joinpath(&full_path, backend->path, ref->name) < 0) - return -1; /* critical; do not try to recover on oom */ + git_filebuf_cleanup(&lock); - if (git_path_exists(full_path.ptr) && p_unlink(full_path.ptr) < 0) { - if (failed) - continue; + /* We need to stop anybody from updating the ref while we try to do a safe delete */ + error = loose_lock(&lock, backend, ref->name); + /* If someone else is updating it, let them do it */ + if (error == GIT_EEXISTS || error == GIT_ENOTFOUND) + continue; - giterr_set(GITERR_REFERENCE, - "Failed to remove loose reference '%s' after packing: %s", - full_path.ptr, strerror(errno)); - failed = 1; + if (error < 0) { + git_buf_free(&ref_content); + giterr_set(GITERR_REFERENCE, "failed to lock loose reference '%s'", ref->name); + return error; } + error = git_futils_readbuffer(&ref_content, lock.path_original); + /* Someone else beat us to cleaning up the ref, let's simply continue */ + if (error == GIT_ENOTFOUND) + continue; + + /* This became a symref between us packing and trying to delete it, so ignore it */ + if (!git__prefixcmp(ref_content.ptr, GIT_SYMREF)) + continue; + + /* Figure out the current id; if we find a bad ref file, skip it so we can do the rest */ + if (loose_parse_oid(¤t_id, lock.path_original, &ref_content) < 0) + continue; + + /* If the ref moved since we packed it, we must not delete it */ + if (!git_oid_equal(¤t_id, &ref->oid)) + continue; + /* * if we fail to remove a single file, this is *not* good, * but we should keep going and remove as many as possible. - * After we've removed as many files as possible, we return - * the error code anyway. + * If we fail to remove, the ref is still in the old state, so + * we haven't lost information. */ + p_unlink(lock.path_original); } - git_buf_free(&full_path); - return failed ? -1 : 0; + git_buf_free(&ref_content); + git_filebuf_cleanup(&lock); + return 0; } /* @@ -944,41 +970,42 @@ static int packed_write(refdb_fs_backend *backend) { git_sortedcache *refcache = backend->refcache; git_filebuf pack_file = GIT_FILEBUF_INIT; + int error; size_t i; /* lock the cache to updates while we do this */ - if (git_sortedcache_wlock(refcache) < 0) - return -1; + if ((error = git_sortedcache_wlock(refcache)) < 0) + return error; /* Open the file! */ - if (git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE) < 0) + if ((error = git_filebuf_open(&pack_file, git_sortedcache_path(refcache), 0, GIT_PACKEDREFS_FILE_MODE)) < 0) goto fail; /* Packfiles have a header... apparently * This is in fact not required, but we might as well print it * just for kicks */ - if (git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER) < 0) + if ((error = git_filebuf_printf(&pack_file, "%s\n", GIT_PACKEDREFS_HEADER)) < 0) goto fail; for (i = 0; i < git_sortedcache_entrycount(refcache); ++i) { struct packref *ref = git_sortedcache_entry(refcache, i); assert(ref); - if (packed_find_peel(backend, ref) < 0) + if ((error = packed_find_peel(backend, ref)) < 0) goto fail; - if (packed_write_ref(ref, &pack_file) < 0) + if ((error = packed_write_ref(ref, &pack_file)) < 0) goto fail; } /* if we've written all the references properly, we can commit * the packfile to make the changes effective */ - if (git_filebuf_commit(&pack_file) < 0) + if ((error = git_filebuf_commit(&pack_file)) < 0) goto fail; /* when and only when the packfile has been properly written, * we can go ahead and remove the loose refs */ - if (packed_remove_loose(backend) < 0) + if ((error = packed_remove_loose(backend)) < 0) goto fail; git_sortedcache_updated(refcache); @@ -991,7 +1018,7 @@ fail: git_filebuf_cleanup(&pack_file); git_sortedcache_wunlock(refcache); - return -1; + return error; } static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, const git_oid *old, const git_oid *new, const git_signature *author, const char *message); @@ -1143,8 +1170,7 @@ static int refdb_fs_backend__write( assert(backend); - error = reference_path_available(backend, ref->name, NULL, force); - if (error < 0) + if ((error = reference_path_available(backend, ref->name, NULL, force)) < 0) return error; /* We need to perform the reflog append and old value check under the ref's lock */ @@ -1260,15 +1286,14 @@ static int refdb_fs_backend__delete_tail( if (git_buf_joinpath(&loose_path, backend->path, ref_name) < 0) return -1; - if (git_path_isfile(loose_path.ptr)) { - error = p_unlink(loose_path.ptr); - loose_deleted = 1; - } - git_buf_free(&loose_path); - - if (error != 0) + error = p_unlink(loose_path.ptr); + if (error < 0 && errno == ENOENT) + error = 0; + else if (error < 0) goto cleanup; + else if (error == 0) + loose_deleted = 1; if ((error = packed_reload(backend)) < 0) goto cleanup; @@ -1291,6 +1316,7 @@ static int refdb_fs_backend__delete_tail( error = packed_write(backend); cleanup: + git_buf_free(&loose_path); git_filebuf_cleanup(file); return error; @@ -1362,14 +1388,15 @@ static int refdb_fs_backend__rename( static int refdb_fs_backend__compress(git_refdb_backend *_backend) { + int error; refdb_fs_backend *backend = (refdb_fs_backend *)_backend; assert(backend); - if (packed_reload(backend) < 0 || /* load the existing packfile */ - packed_loadloose(backend) < 0 || /* add all the loose refs */ - packed_write(backend) < 0) /* write back to disk */ - return -1; + if ((error = packed_reload(backend)) < 0 || /* load the existing packfile */ + (error = packed_loadloose(backend)) < 0 || /* add all the loose refs */ + (error = packed_write(backend)) < 0) /* write back to disk */ + return error; return 0; } @@ -1789,9 +1816,10 @@ static int reflog_append(refdb_fs_backend *backend, const git_reference *ref, co * there maybe an obsolete/unused directory (or directory hierarchy) in the way. */ if (git_path_isdir(git_buf_cstr(&path))) { - if ((git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY) < 0)) - error = -1; - else if (git_path_isdir(git_buf_cstr(&path))) { + if ((error = git_futils_rmdir_r(git_buf_cstr(&path), NULL, GIT_RMDIR_SKIP_NONEMPTY)) < 0) { + if (error == GIT_ENOTFOUND) + error = 0; + } else if (git_path_isdir(git_buf_cstr(&path))) { giterr_set(GITERR_REFERENCE, "cannot create reflog at '%s', there are reflogs beneath that folder", ref->name); error = GIT_EDIRECTORY; diff --git a/src/refs.c b/src/refs.c index 26c80021f..bff443ac9 100644 --- a/src/refs.c +++ b/src/refs.c @@ -105,6 +105,18 @@ git_reference *git_reference__set_name( return rewrite; } +int git_reference_dup(git_reference **dest, git_reference *source) +{ + if (source->type == GIT_REF_SYMBOLIC) + *dest = git_reference__alloc_symbolic(source->name, source->target.symbolic); + else + *dest = git_reference__alloc(source->name, &source->target.oid, &source->peel); + + GITERR_CHECK_ALLOC(*dest); + + return 0; +} + void git_reference_free(git_reference *reference) { if (reference == NULL) @@ -448,7 +460,7 @@ int git_reference_create_matching( { int error; git_signature *who = NULL; - + assert(id); if ((error = git_reference__log_signature(&who, repo)) < 0) diff --git a/src/remote.c b/src/remote.c index 15a00dd28..c1d7d59ea 100644 --- a/src/remote.c +++ b/src/remote.c @@ -547,53 +547,6 @@ static int lookup_remote_prune_config(git_remote *remote, git_config *config, co return error; } -static int update_config_refspec(const git_remote *remote, git_config *config, int direction) -{ - git_buf name = GIT_BUF_INIT; - unsigned int push; - const char *dir; - size_t i; - int error = 0; - const char *cname; - - push = direction == GIT_DIRECTION_PUSH; - dir = push ? "push" : "fetch"; - - if (git_buf_printf(&name, "remote.%s.%s", remote->name, dir) < 0) - return -1; - cname = git_buf_cstr(&name); - - /* Clear out the existing config */ - while (!error) - error = git_config_delete_multivar(config, cname, ".*"); - - if (error != GIT_ENOTFOUND) - return error; - - for (i = 0; i < remote->refspecs.length; i++) { - git_refspec *spec = git_vector_get(&remote->refspecs, i); - - if (spec->push != push) - continue; - - // "$^" is a unmatcheable regexp: it will not match anything at all, so - // all values will be considered new and we will not replace any - // present value. - if ((error = git_config_set_multivar( - config, cname, "$^", spec->string)) < 0) { - goto cleanup; - } - } - - giterr_clear(); - error = 0; - -cleanup: - git_buf_free(&name); - - return error; -} - const char *git_remote_name(const git_remote *remote) { assert(remote); @@ -695,7 +648,7 @@ static int set_transport_custom_headers(git_transport *t, const git_strarray *cu return t->set_custom_headers(t, custom_headers); } -int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_strarray *custom_headers) +int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_proxy_options *proxy, const git_strarray *custom_headers) { git_transport *t; const char *url; @@ -714,6 +667,9 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re payload = callbacks->payload; } + if (proxy) + GITERR_CHECK_VERSION(proxy, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + t = remote->transport; url = git_remote__urlfordirection(remote, direction); @@ -738,7 +694,7 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re goto on_error; if ((error = set_transport_callbacks(t, callbacks)) < 0 || - (error = t->connect(t, url, credentials, payload, direction, flags)) != 0) + (error = t->connect(t, url, credentials, payload, proxy, direction, flags)) != 0) goto on_error; remote->transport = t; @@ -896,6 +852,7 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; const git_remote_callbacks *cbs = NULL; const git_strarray *custom_headers = NULL; + const git_proxy_options *proxy = NULL; assert(remote); @@ -903,10 +860,12 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; custom_headers = &opts->custom_headers; + GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + proxy = &opts->proxy_opts; } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, proxy, custom_headers)) < 0) goto on_error; if (ls_to_vector(&refs, remote) < 0) @@ -971,6 +930,7 @@ int git_remote_fetch( git_buf reflog_msg_buf = GIT_BUF_INIT; const git_remote_callbacks *cbs = NULL; const git_strarray *custom_headers = NULL; + const git_proxy_options *proxy = NULL; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); @@ -978,10 +938,12 @@ int git_remote_fetch( custom_headers = &opts->custom_headers; update_fetchhead = opts->update_fetchhead; tagopt = opts->download_tags; + GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + proxy = &opts->proxy_opts; } /* Connect and download everything */ - if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) != 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, proxy, custom_headers)) != 0) return error; error = git_remote_download(remote, refspecs, opts); @@ -2094,34 +2056,6 @@ int git_remote_add_push(git_repository *repo, const char *remote, const char *re return write_add_refspec(repo, remote, refspec, false); } -static int set_refspecs(git_remote *remote, git_strarray *array, int push) -{ - git_vector *vec = &remote->refspecs; - git_refspec *spec; - size_t i; - - /* Start by removing any refspecs of the same type */ - for (i = 0; i < vec->length; i++) { - spec = git_vector_get(vec, i); - if (spec->push != push) - continue; - - git_refspec__free(spec); - git__free(spec); - git_vector_remove(vec, i); - i--; - } - - /* And now we add the new ones */ - - for (i = 0; i < array->count; i++) { - if (add_refspec(remote, array->strings[i], !push) < 0) - return -1; - } - - return 0; -} - static int copy_refspecs(git_strarray *array, const git_remote *remote, unsigned int push) { size_t i; @@ -2403,16 +2337,18 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi git_refspec *spec; const git_remote_callbacks *cbs = NULL; const git_strarray *custom_headers = NULL; + const git_proxy_options *proxy = NULL; assert(remote); if (opts) { cbs = &opts->callbacks; custom_headers = &opts->custom_headers; + proxy = &opts->proxy_opts; } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0) goto cleanup; free_refspecs(&remote->active_refspecs); @@ -2462,16 +2398,19 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ int error; const git_remote_callbacks *cbs = NULL; const git_strarray *custom_headers = NULL; + const git_proxy_options *proxy = NULL; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; custom_headers = &opts->custom_headers; + GITERR_CHECK_VERSION(&opts->proxy_opts, GIT_PROXY_OPTIONS_VERSION, "git_proxy_options"); + proxy = &opts->proxy_opts; } assert(remote && refspecs); - if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, proxy, custom_headers)) < 0) return error; if ((error = git_remote_upload(remote, refspecs, opts)) < 0) diff --git a/src/repository.c b/src/repository.c index c19790a8c..5c4442360 100644 --- a/src/repository.c +++ b/src/repository.c @@ -357,6 +357,7 @@ static int find_repo( { int error; git_buf path = GIT_BUF_INIT; + git_buf repo_link = GIT_BUF_INIT; struct stat st; dev_t initial_device = 0; int min_iterations; @@ -365,16 +366,18 @@ static int find_repo( git_buf_free(repo_path); - if ((error = git_path_prettify(&path, start_path, NULL)) < 0) + error = git_path_prettify(&path, start_path, NULL); + if (error < 0) return error; /* in_dot_git toggles each loop: * /a/b/c/.git, /a/b/c, /a/b/.git, /a/b, /a/.git, /a - * With GIT_REPOSITORY_OPEN_BARE, we assume we started with /a/b/c.git - * and don't append .git the first time through. + * With GIT_REPOSITORY_OPEN_BARE or GIT_REPOSITORY_OPEN_NO_DOTGIT, we + * assume we started with /a/b/c.git and don't append .git the first + * time through. * min_iterations indicates the number of iterations left before going * further counts as a search. */ - if (flags & GIT_REPOSITORY_OPEN_BARE) { + if (flags & (GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT)) { in_dot_git = true; min_iterations = 1; } else { @@ -382,19 +385,22 @@ static int find_repo( min_iterations = 2; } - while (!error && (min_iterations || !(path.ptr[ceiling_offset] == 0 || - (flags & GIT_REPOSITORY_OPEN_NO_SEARCH)))) { - if (!in_dot_git) - if ((error = git_buf_joinpath(&path, path.ptr, DOT_GIT)) < 0) - break; - in_dot_git = !in_dot_git; + for (;;) { + if (!(flags & GIT_REPOSITORY_OPEN_NO_DOTGIT)) { + if (!in_dot_git) { + error = git_buf_joinpath(&path, path.ptr, DOT_GIT); + if (error < 0) + break; + } + in_dot_git = !in_dot_git; + } if (p_stat(path.ptr, &st) == 0) { /* check that we have not crossed device boundaries */ if (initial_device == 0) initial_device = st.st_dev; else if (st.st_dev != initial_device && - (flags & GIT_REPOSITORY_OPEN_CROSS_FS) == 0) + !(flags & GIT_REPOSITORY_OPEN_CROSS_FS)) break; if (S_ISDIR(st.st_mode)) { @@ -404,26 +410,23 @@ static int find_repo( break; } } - else if (S_ISREG(st.st_mode)) { - git_buf repo_link = GIT_BUF_INIT; - - if (!(error = read_gitfile(&repo_link, path.ptr))) { - if (valid_repository_path(&repo_link)) { - git_buf_swap(repo_path, &repo_link); - - if (link_path) - error = git_buf_put(link_path, - path.ptr, path.size); - } - - git_buf_free(&repo_link); + else if (S_ISREG(st.st_mode) && git__suffixcmp(path.ptr, "/" DOT_GIT) == 0) { + error = read_gitfile(&repo_link, path.ptr); + if (error < 0) break; + if (valid_repository_path(&repo_link)) { + git_buf_swap(repo_path, &repo_link); + + if (link_path) + error = git_buf_put(link_path, path.ptr, path.size); } - git_buf_free(&repo_link); + break; } } - /* move up one directory level */ + /* Move up one directory. If we're in_dot_git, we'll search the + * parent itself next. If we're !in_dot_git, we'll search .git + * in the parent directory next (added at the top of the loop). */ if (git_path_dirname_r(&path, path.ptr) < 0) { error = -1; break; @@ -433,6 +436,12 @@ static int find_repo( * find the ceiling for a search. */ if (min_iterations && (--min_iterations == 0)) ceiling_offset = find_ceiling_dir_offset(path.ptr, ceiling_dirs); + + /* Check if we should stop searching here. */ + if (min_iterations == 0 + && (path.ptr[ceiling_offset] == 0 + || (flags & GIT_REPOSITORY_OPEN_NO_SEARCH))) + break; } if (!error && parent_path && !(flags & GIT_REPOSITORY_OPEN_BARE)) { @@ -446,14 +455,16 @@ static int find_repo( return -1; } - git_buf_free(&path); - + /* If we didn't find the repository, and we don't have any other error + * to report, report that. */ if (!git_buf_len(repo_path) && !error) { giterr_set(GITERR_REPOSITORY, "Could not find repository from '%s'", start_path); error = GIT_ENOTFOUND; } + git_buf_free(&path); + git_buf_free(&repo_link); return error; } @@ -488,6 +499,175 @@ int git_repository_open_bare( return 0; } +static int _git_repository_open_ext_from_env( + git_repository **out, + const char *start_path) +{ + git_repository *repo = NULL; + git_index *index = NULL; + git_odb *odb = NULL; + git_buf dir_buf = GIT_BUF_INIT; + git_buf ceiling_dirs_buf = GIT_BUF_INIT; + git_buf across_fs_buf = GIT_BUF_INIT; + git_buf index_file_buf = GIT_BUF_INIT; + git_buf namespace_buf = GIT_BUF_INIT; + git_buf object_dir_buf = GIT_BUF_INIT; + git_buf alts_buf = GIT_BUF_INIT; + git_buf work_tree_buf = GIT_BUF_INIT; + git_buf common_dir_buf = GIT_BUF_INIT; + const char *ceiling_dirs = NULL; + unsigned flags = 0; + int error; + + if (!start_path) { + error = git__getenv(&dir_buf, "GIT_DIR"); + if (error == GIT_ENOTFOUND) { + giterr_clear(); + start_path = "."; + } else if (error < 0) + goto error; + else { + start_path = git_buf_cstr(&dir_buf); + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + flags |= GIT_REPOSITORY_OPEN_NO_DOTGIT; + } + } + + error = git__getenv(&ceiling_dirs_buf, "GIT_CEILING_DIRECTORIES"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else + ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); + + error = git__getenv(&across_fs_buf, "GIT_DISCOVERY_ACROSS_FILESYSTEM"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else { + int across_fs = 0; + error = git_config_parse_bool(&across_fs, git_buf_cstr(&across_fs_buf)); + if (error < 0) + goto error; + if (across_fs) + flags |= GIT_REPOSITORY_OPEN_CROSS_FS; + } + + error = git__getenv(&index_file_buf, "GIT_INDEX_FILE"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else { + error = git_index_open(&index, git_buf_cstr(&index_file_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&namespace_buf, "GIT_NAMESPACE"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + + error = git__getenv(&object_dir_buf, "GIT_OBJECT_DIRECTORY"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else { + error = git_odb_open(&odb, git_buf_cstr(&object_dir_buf)); + if (error < 0) + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_WORK_TREE"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else { + giterr_set(GITERR_INVALID, "GIT_WORK_TREE unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git__getenv(&work_tree_buf, "GIT_COMMON_DIR"); + if (error == GIT_ENOTFOUND) + giterr_clear(); + else if (error < 0) + goto error; + else { + giterr_set(GITERR_INVALID, "GIT_COMMON_DIR unimplemented"); + error = GIT_ERROR; + goto error; + } + + error = git_repository_open_ext(&repo, start_path, flags, ceiling_dirs); + if (error < 0) + goto error; + + if (odb) + git_repository_set_odb(repo, odb); + + error = git__getenv(&alts_buf, "GIT_ALTERNATE_OBJECT_DIRECTORIES"); + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } else if (error < 0) + goto error; + else { + const char *end; + char *alt, *sep; + if (!odb) { + error = git_repository_odb(&odb, repo); + if (error < 0) + goto error; + } + + end = git_buf_cstr(&alts_buf) + git_buf_len(&alts_buf); + for (sep = alt = alts_buf.ptr; sep != end; alt = sep+1) { + for (sep = alt; *sep && *sep != GIT_PATH_LIST_SEPARATOR; sep++) + ; + if (*sep) + *sep = '\0'; + error = git_odb_add_disk_alternate(odb, alt); + if (error < 0) + goto error; + } + } + + if (git_buf_len(&namespace_buf)) { + error = git_repository_set_namespace(repo, git_buf_cstr(&namespace_buf)); + if (error < 0) + goto error; + } + + git_repository_set_index(repo, index); + + if (out) { + *out = repo; + goto success; + } +error: + git_repository_free(repo); +success: + git_odb_free(odb); + git_index_free(index); + git_buf_free(&common_dir_buf); + git_buf_free(&work_tree_buf); + git_buf_free(&alts_buf); + git_buf_free(&object_dir_buf); + git_buf_free(&namespace_buf); + git_buf_free(&index_file_buf); + git_buf_free(&across_fs_buf); + git_buf_free(&ceiling_dirs_buf); + git_buf_free(&dir_buf); + return error; +} + int git_repository_open_ext( git_repository **repo_ptr, const char *start_path, @@ -500,6 +680,9 @@ int git_repository_open_ext( git_repository *repo; git_config *config = NULL; + if (flags & GIT_REPOSITORY_OPEN_FROM_ENV) + return _git_repository_open_ext_from_env(repo_ptr, start_path); + if (repo_ptr) *repo_ptr = NULL; @@ -2170,7 +2353,7 @@ int git_repository_set_head_detached_from_annotated( { assert(repo && commitish); - return detach(repo, git_annotated_commit_id(commitish), commitish->ref_name); + return detach(repo, git_annotated_commit_id(commitish), commitish->description); } int git_repository_detach_head(git_repository* repo) diff --git a/src/reset.c b/src/reset.c index f8a1a1dc8..db0bfb373 100644 --- a/src/reset.c +++ b/src/reset.c @@ -195,5 +195,5 @@ int git_reset_from_annotated( git_reset_t reset_type, const git_checkout_options *checkout_opts) { - return reset(repo, (git_object *) commit->commit, commit->ref_name, reset_type, checkout_opts); + return reset(repo, (git_object *) commit->commit, commit->description, reset_type, checkout_opts); } diff --git a/src/revparse.c b/src/revparse.c index e0ec3941d..aa7e0bd98 100644 --- a/src/revparse.c +++ b/src/revparse.c @@ -50,7 +50,7 @@ static int build_regex(regex_t *regex, const char *pattern) return GIT_EINVALIDSPEC; } - error = regcomp(regex, pattern, REG_EXTENDED); + error = p_regcomp(regex, pattern, REG_EXTENDED); if (!error) return 0; diff --git a/src/revwalk.c b/src/revwalk.c index 4815a1089..f5502a72e 100644 --- a/src/revwalk.c +++ b/src/revwalk.c @@ -13,6 +13,7 @@ #include "revwalk.h" #include "git2/revparse.h" #include "merge.h" +#include "vector.h" GIT__USE_OIDMAP @@ -41,97 +42,6 @@ git_commit_list_node *git_revwalk__commit_lookup( return commit; } -typedef git_array_t(git_commit_list_node*) commit_list_node_array; - -static bool interesting_arr(commit_list_node_array arr) -{ - git_commit_list_node **n; - size_t i = 0, size; - - size = git_array_size(arr); - for (i = 0; i < size; i++) { - n = git_array_get(arr, i); - if (!*n) - break; - - if (!(*n)->uninteresting) - return true; - } - - return false; -} - -static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit) -{ - int error; - unsigned short i; - commit_list_node_array pending = GIT_ARRAY_INIT; - git_commit_list_node **tmp; - - assert(commit); - - do { - commit->uninteresting = 1; - - if ((error = git_commit_list_parse(walk, commit)) < 0) - return error; - - for (i = 0; i < commit->out_degree; ++i) - if (!commit->parents[i]->uninteresting) { - git_commit_list_node **node = git_array_alloc(pending); - GITERR_CHECK_ALLOC(node); - *node = commit->parents[i]; - } - - tmp = git_array_pop(pending); - commit = tmp ? *tmp : NULL; - - } while (commit != NULL && !interesting_arr(pending)); - - git_array_clear(pending); - - return 0; -} - -static int process_commit(git_revwalk *walk, git_commit_list_node *commit, int hide) -{ - int error; - - if (!hide && walk->hide_cb) - hide = walk->hide_cb(&commit->oid, walk->hide_cb_payload); - - if (hide && mark_uninteresting(walk, commit) < 0) - return -1; - - if (commit->seen) - return 0; - - commit->seen = 1; - - if ((error = git_commit_list_parse(walk, commit)) < 0) - return error; - - if (!hide) - return walk->enqueue(walk, commit); - - return 0; -} - -static int process_commit_parents(git_revwalk *walk, git_commit_list_node *commit) -{ - unsigned short i, max; - int error = 0; - - max = commit->out_degree; - if (walk->first_parent && commit->out_degree) - max = 1; - - for (i = 0; i < max && !error; ++i) - error = process_commit(walk, commit->parents[i], commit->uninteresting); - - return error; -} - static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting, int from_glob) { git_oid commit_id; @@ -321,17 +231,12 @@ static int revwalk_enqueue_unsorted(git_revwalk *walk, git_commit_list_node *com static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk *walk) { - int error; git_commit_list_node *next; - while ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) - if (!next->uninteresting) { - if ((error = process_commit_parents(walk, next)) < 0) - return error; - - *object_out = next; - return 0; - } + if ((next = git_pqueue_pop(&walk->iterator_time)) != NULL) { + *object_out = next; + return 0; + } giterr_clear(); return GIT_ITEROVER; @@ -339,17 +244,15 @@ static int revwalk_next_timesort(git_commit_list_node **object_out, git_revwalk static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk *walk) { - int error; git_commit_list_node *next; - while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL) + while ((next = git_commit_list_pop(&walk->iterator_rand)) != NULL) { + /* Some commits might become uninteresting after being added to the list */ if (!next->uninteresting) { - if ((error = process_commit_parents(walk, next)) < 0) - return error; - *object_out = next; return 0; } + } giterr_clear(); return GIT_ITEROVER; @@ -358,38 +261,17 @@ static int revwalk_next_unsorted(git_commit_list_node **object_out, git_revwalk static int revwalk_next_toposort(git_commit_list_node **object_out, git_revwalk *walk) { git_commit_list_node *next; - unsigned short i, max; - for (;;) { - next = git_commit_list_pop(&walk->iterator_topo); - if (next == NULL) { - giterr_clear(); - return GIT_ITEROVER; + while ((next = git_commit_list_pop(&walk->iterator_topo)) != NULL) { + /* Some commits might become uninteresting after being added to the list */ + if (!next->uninteresting) { + *object_out = next; + return 0; } - - if (next->in_degree > 0) { - next->topo_delay = 1; - continue; - } - - - max = next->out_degree; - if (walk->first_parent && next->out_degree) - max = 1; - - for (i = 0; i < max; ++i) { - git_commit_list_node *parent = next->parents[i]; - - if (--parent->in_degree == 0 && parent->topo_delay) { - parent->topo_delay = 0; - if (git_commit_list_insert(parent, &walk->iterator_topo) == NULL) - return -1; - } - } - - *object_out = next; - return 0; } + + giterr_clear(); + return GIT_ITEROVER; } static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk *walk) @@ -398,81 +280,264 @@ static int revwalk_next_reverse(git_commit_list_node **object_out, git_revwalk * return *object_out ? 0 : GIT_ITEROVER; } - -static int interesting(git_pqueue *list) +static void mark_parents_uninteresting(git_commit_list_node *commit) { - size_t i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if (!commit->uninteresting) - return 1; - } - - return 0; -} - -static int contains(git_pqueue *list, git_commit_list_node *node) -{ - size_t i; - - for (i = 0; i < git_pqueue_size(list); i++) { - git_commit_list_node *commit = git_pqueue_get(list, i); - if (commit == node) - return 1; - } - - return 0; -} - -static int premark_uninteresting(git_revwalk *walk) -{ - int error = 0; unsigned short i; - git_pqueue q; - git_commit_list *list; - git_commit_list_node *commit, *parent; + git_commit_list *parents = NULL; - if ((error = git_pqueue_init(&q, 0, 8, git_commit_list_time_cmp)) < 0) - return error; + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); - for (list = walk->user_input; list; list = list->next) { - if ((error = git_commit_list_parse(walk, list->item)) < 0) - goto cleanup; - if ((error = git_pqueue_insert(&q, list->item)) < 0) - goto cleanup; + while (parents) { + commit = git_commit_list_pop(&parents); + + while (commit) { + if (commit->uninteresting) + break; + + commit->uninteresting = 1; + /* + * If we've reached this commit some other way + * already, we need to mark its parents uninteresting + * as well. + */ + if (!commit->parents) + break; + + for (i = 0; i < commit->out_degree; i++) + git_commit_list_insert(commit->parents[i], &parents); + commit = commit->parents[0]; + } + } +} + +static int add_parents_to_list(git_revwalk *walk, git_commit_list_node *commit, git_commit_list **list) +{ + unsigned short i; + int error; + + if (commit->added) + return 0; + + commit->added = 1; + + /* + * Go full on in the uninteresting case as we want to include + * as many of these as we can. + * + * Usually we haven't parsed the parent of a parent, but if we + * have it we reached it via other means so we want to mark + * its parents recursively too. + */ + if (commit->uninteresting) { + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; + p->uninteresting = 1; + + /* git does it gently here, but we don't like missing objects */ + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; + + if (p->parents) + mark_parents_uninteresting(p); + + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } + + return 0; } - while (interesting(&q)) { - commit = git_pqueue_pop(&q); + /* + * Now on to what we do if the commit is indeed + * interesting. Here we do want things like first-parent take + * effect as this is what we'll be showing. + */ + for (i = 0; i < commit->out_degree; i++) { + git_commit_list_node *p = commit->parents[i]; - for (i = 0; i < commit->out_degree; i++) { - parent = commit->parents[i]; + if ((error = git_commit_list_parse(walk, p)) < 0) + return error; - if ((error = git_commit_list_parse(walk, parent)) < 0) - goto cleanup; + if (walk->hide_cb && walk->hide_cb(&p->oid, walk->hide_cb_payload)) + continue; - if (commit->uninteresting) - parent->uninteresting = 1; + if (!p->seen) { + p->seen = 1; + git_commit_list_insert_by_date(p, list); + } - if (contains(&q, parent)) + if (walk->first_parent) + break; + } + return 0; +} + +static int everybody_uninteresting(git_commit_list *orig) +{ + git_commit_list *list = orig; + + while (list) { + git_commit_list_node *commit = list->item; + list = list->next; + if (!commit->uninteresting) + return 0; + } + + return 1; +} + +/* How many unintersting commits we want to look at after we run out of interesting ones */ +#define SLOP 5 + +static int still_interesting(git_commit_list *list, int64_t time, int slop) +{ + /* The empty list is pretty boring */ + if (!list) + return 0; + + /* + * If the destination list has commits with an earlier date + * than our source we want to continue looking. + */ + if (time <= list->item->time) + return SLOP; + + /* If we find interesting commits, we reset the slop count */ + if (!everybody_uninteresting(list)) + return SLOP; + + /* Everything's uninteresting, reduce the count */ + return slop - 1; +} + +static int limit_list(git_commit_list **out, git_revwalk *walk, git_commit_list *commits) +{ + int error, slop = SLOP; + int64_t time = ~0ll; + git_commit_list *list = commits; + git_commit_list *newlist = NULL; + git_commit_list **p = &newlist; + + while (list) { + git_commit_list_node *commit = git_commit_list_pop(&list); + + if ((error = add_parents_to_list(walk, commit, &list)) < 0) + return error; + + if (commit->uninteresting) { + mark_parents_uninteresting(commit); + + slop = still_interesting(list, time, slop); + if (slop) continue; - if ((error = git_pqueue_insert(&q, parent)) < 0) + break; + } + + if (!commit->uninteresting && walk->hide_cb && walk->hide_cb(&commit->oid, walk->hide_cb_payload)) + continue; + + time = commit->time; + p = &git_commit_list_insert(commit, p)->next; + } + + git_commit_list_free(&list); + *out = newlist; + return 0; +} + +static int sort_in_topological_order(git_commit_list **out, git_revwalk *walk, git_commit_list *list) +{ + git_commit_list *ll = NULL, *newlist, **pptr; + git_commit_list_node *next; + git_pqueue queue; + git_vector_cmp queue_cmp = NULL; + unsigned short i; + int error; + + if (walk->sorting & GIT_SORT_TIME) + queue_cmp = git_commit_list_time_cmp; + + if ((error = git_pqueue_init(&queue, 0, 8, queue_cmp))) + return error; + + /* + * Start by resetting the in-degree to 1 for the commits in + * our list. We want to go through this list again, so we + * store it in the commit list as we extract it from the lower + * machinery. + */ + for (ll = list; ll; ll = ll->next) { + ll->item->in_degree = 1; + } + + /* + * Count up how many children each commit has. We limit + * ourselves to those commits in the original list (in-degree + * of 1) avoiding setting it for any parent that was hidden. + */ + for(ll = list; ll; ll = ll->next) { + for (i = 0; i < ll->item->out_degree; ++i) { + git_commit_list_node *parent = ll->item->parents[i]; + if (parent->in_degree) + parent->in_degree++; + } + } + + /* + * Now we find the tips i.e. those not reachable from any other node + * i.e. those which still have an in-degree of 1. + */ + for(ll = list; ll; ll = ll->next) { + if (ll->item->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, ll->item))) goto cleanup; } } + /* + * We need to output the tips in the order that they came out of the + * traversal, so if we're not doing time-sorting, we need to reverse the + * pqueue in order to get them to come out as we inserted them. + */ + if ((walk->sorting & GIT_SORT_TIME) == 0) + git_pqueue_reverse(&queue); + + + pptr = &newlist; + newlist = NULL; + while ((next = git_pqueue_pop(&queue)) != NULL) { + for (i = 0; i < next->out_degree; ++i) { + git_commit_list_node *parent = next->parents[i]; + if (parent->in_degree == 0) + continue; + + if (--parent->in_degree == 1) { + if ((error = git_pqueue_insert(&queue, parent))) + goto cleanup; + } + } + + /* All the children of 'item' have been emitted (since we got to it via the priority queue) */ + next->in_degree = 0; + + pptr = &git_commit_list_insert(next, pptr)->next; + } + + *out = newlist; + error = 0; + cleanup: - git_pqueue_free(&q); + git_pqueue_free(&queue); return error; } static int prepare_walk(git_revwalk *walk) { int error; - git_commit_list *list; + git_commit_list *list, *commits = NULL; git_commit_list_node *next; /* If there were no pushes, we know that the walk is already over */ @@ -481,32 +546,42 @@ static int prepare_walk(git_revwalk *walk) return GIT_ITEROVER; } - if (walk->did_hide && (error = premark_uninteresting(walk)) < 0) - return error; - for (list = walk->user_input; list; list = list->next) { - if (process_commit(walk, list->item, list->item->uninteresting) < 0) - return -1; + git_commit_list_node *commit = list->item; + if ((error = git_commit_list_parse(walk, commit)) < 0) + return error; + + if (commit->uninteresting) + mark_parents_uninteresting(commit); + + if (!commit->seen) { + commit->seen = 1; + git_commit_list_insert(commit, &commits); + } } + if ((error = limit_list(&commits, walk, commits)) < 0) + return error; if (walk->sorting & GIT_SORT_TOPOLOGICAL) { - unsigned short i; + error = sort_in_topological_order(&walk->iterator_topo, walk, commits); + git_commit_list_free(&commits); - while ((error = walk->get_next(&next, walk)) == 0) { - for (i = 0; i < next->out_degree; ++i) { - git_commit_list_node *parent = next->parents[i]; - parent->in_degree++; - } - - if (git_commit_list_insert(next, &walk->iterator_topo) == NULL) - return -1; - } - - if (error != GIT_ITEROVER) + if (error < 0) return error; walk->get_next = &revwalk_next_toposort; + } else if (walk->sorting & GIT_SORT_TIME) { + for (list = commits; list && !error; list = list->next) + error = walk->enqueue(walk, list->item); + + git_commit_list_free(&commits); + + if (error < 0) + return error; + } else { + walk->iterator_rand = commits; + walk->get_next = revwalk_next_unsorted; } if (walk->sorting & GIT_SORT_REVERSE) { @@ -632,6 +707,7 @@ void git_revwalk_reset(git_revwalk *walk) commit->in_degree = 0; commit->topo_delay = 0; commit->uninteresting = 0; + commit->added = 0; commit->flags = 0; }); diff --git a/src/settings.c b/src/settings.c index 0da19ea03..4a6e0f353 100644 --- a/src/settings.c +++ b/src/settings.c @@ -23,7 +23,7 @@ void git_libgit2_version(int *major, int *minor, int *rev) *rev = LIBGIT2_VER_REVISION; } -int git_libgit2_features() +int git_libgit2_features(void) { return 0 #ifdef GIT_THREADS @@ -73,12 +73,12 @@ static int config_level_to_sysdir(int config_level) extern char *git__user_agent; extern char *git__ssl_ciphers; -const char *git_libgit2__user_agent() +const char *git_libgit2__user_agent(void) { return git__user_agent; } -const char *git_libgit2__ssl_ciphers() +const char *git_libgit2__ssl_ciphers(void) { return git__ssl_ciphers; } @@ -209,6 +209,14 @@ int git_libgit2_opts(int key, ...) #endif break; + case GIT_OPT_GET_USER_AGENT: + { + git_buf *out = va_arg(ap, git_buf *); + git_buf_sanitize(out); + error = git_buf_sets(out, git__user_agent); + } + break; + default: giterr_set(GITERR_INVALID, "invalid option key"); error = -1; diff --git a/src/signature.c b/src/signature.c index d07c93323..22cba7ef3 100644 --- a/src/signature.c +++ b/src/signature.c @@ -200,7 +200,8 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, memset(sig, 0, sizeof(git_signature)); - if ((buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) + if (ender && + (buffer_end = memchr(buffer, ender, buffer_end - buffer)) == NULL) return signature_error("no newline given"); if (header) { @@ -250,7 +251,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, * only store timezone if it's not overflowing; * see http://www.worldtimezone.com/faq.html */ - if (hours < 14 && mins < 59) { + if (hours <= 14 && mins <= 59) { sig->when.offset = (hours * 60) + mins; if (tz_start[0] == '-') sig->when.offset = -sig->when.offset; @@ -262,6 +263,30 @@ int git_signature__parse(git_signature *sig, const char **buffer_out, return 0; } +int git_signature_from_buffer(git_signature **out, const char *buf) +{ + git_signature *sig; + const char *buf_end; + int error; + + assert(out && buf); + + *out = NULL; + + sig = git__calloc(1, sizeof(git_signature)); + GITERR_CHECK_ALLOC(sig); + + buf_end = buf + strlen(buf); + error = git_signature__parse(sig, &buf, buf_end, NULL, '\0'); + + if (error) + git__free(sig); + else + *out = sig; + + return error; +} + void git_signature__writebuf(git_buf *buf, const char *header, const git_signature *sig) { int offset, hours, mins; diff --git a/src/sortedcache.c b/src/sortedcache.c index 5c2a167a7..5bd989a9f 100644 --- a/src/sortedcache.c +++ b/src/sortedcache.c @@ -200,6 +200,7 @@ void git_sortedcache_runlock(git_sortedcache *sc) int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) { int error, fd; + struct stat st; if ((error = git_sortedcache_wlock(sc)) < 0) return error; @@ -207,19 +208,27 @@ int git_sortedcache_lockandload(git_sortedcache *sc, git_buf *buf) if ((error = git_futils_filestamp_check(&sc->stamp, sc->path)) <= 0) goto unlock; - if (!git__is_sizet(sc->stamp.size)) { - giterr_set(GITERR_INVALID, "Unable to load file larger than size_t"); - error = -1; - goto unlock; - } - if ((fd = git_futils_open_ro(sc->path)) < 0) { error = fd; goto unlock; } + if (p_fstat(fd, &st) < 0) { + giterr_set(GITERR_OS, "failed to stat file"); + error = -1; + (void)p_close(fd); + goto unlock; + } + + if (!git__is_sizet(st.st_size)) { + giterr_set(GITERR_INVALID, "Unable to load file larger than size_t"); + error = -1; + (void)p_close(fd); + goto unlock; + } + if (buf) - error = git_futils_readbuffer_fd(buf, fd, (size_t)sc->stamp.size); + error = git_futils_readbuffer_fd(buf, fd, (size_t)st.st_size); (void)p_close(fd); diff --git a/src/stash.c b/src/stash.c index 43a464e64..f5f4f36bf 100644 --- a/src/stash.c +++ b/src/stash.c @@ -23,6 +23,7 @@ #include "iterator.h" #include "merge.h" #include "diff.h" +#include "diff_generate.h" static int create_error(int error, const char *msg) { diff --git a/src/status.c b/src/status.c index b206b0e2f..e610f5fe1 100644 --- a/src/status.c +++ b/src/status.c @@ -19,6 +19,7 @@ #include "git2/diff.h" #include "diff.h" +#include "diff_generate.h" static unsigned int index_delta2status(const git_diff_delta *head2idx) { diff --git a/src/stransport_stream.c b/src/stransport_stream.c index 9d87d6029..50ed9452c 100644 --- a/src/stransport_stream.c +++ b/src/stransport_stream.c @@ -120,11 +120,13 @@ static int stransport_certificate(git_cert **out, git_stream *stream) return 0; } -static int stransport_set_proxy(git_stream *stream, const char *proxy) +static int stransport_set_proxy( + git_stream *stream, + const git_proxy_options *proxy_opts) { stransport_stream *st = (stransport_stream *) stream; - return git_stream_set_proxy(st->io, proxy); + return git_stream_set_proxy(st->io, proxy_opts); } /* diff --git a/src/stream.h b/src/stream.h index 4692c7115..d35477591 100644 --- a/src/stream.h +++ b/src/stream.h @@ -35,14 +35,14 @@ GIT_INLINE(int) git_stream_supports_proxy(git_stream *st) return st->proxy_support; } -GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const char *proxy_url) +GIT_INLINE(int) git_stream_set_proxy(git_stream *st, const git_proxy_options *proxy_opts) { if (!st->proxy_support) { giterr_set(GITERR_INVALID, "proxy not supported on this stream"); return -1; } - return st->set_proxy(st, proxy_url); + return st->set_proxy(st, proxy_opts); } GIT_INLINE(ssize_t) git_stream_read(git_stream *st, void *data, size_t len) diff --git a/src/submodule.c b/src/submodule.c index c903cf939..86ad53be0 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -93,6 +93,7 @@ static git_config_backend *open_gitmodules(git_repository *repo, int gitmod); static git_config *gitmodules_snapshot(git_repository *repo); static int get_url_base(git_buf *url, git_repository *repo); static int lookup_head_remote_key(git_buf *remote_key, git_repository *repo); +static int lookup_default_remote(git_remote **remote, git_repository *repo); static int submodule_load_each(const git_config_entry *entry, void *payload); static int submodule_read_config(git_submodule *sm, git_config *cfg); static int submodule_load_from_wd_lite(git_submodule *); @@ -1131,7 +1132,7 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio if (error != GIT_ENOTFOUND) goto done; - if (error == GIT_ENOTFOUND && !init) { + if (!init) { giterr_set(GITERR_SUBMODULE, "Submodule is not initialized."); error = GIT_ERROR; goto done; @@ -1171,9 +1172,20 @@ int git_submodule_update(git_submodule *sm, int init, git_submodule_update_optio * update the workdir contents of the subrepository, and set the subrepository's * head to the new commit. */ - if ((error = git_submodule_open(&sub_repo, sm)) < 0 || - (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0 || - (error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || + if ((error = git_submodule_open(&sub_repo, sm)) < 0) + goto done; + + /* Look up the target commit in the submodule. */ + if ((error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0) { + /* If it isn't found then fetch and try again. */ + if (error != GIT_ENOTFOUND || !update_options.allow_fetch || + (error = lookup_default_remote(&remote, sub_repo)) < 0 || + (error = git_remote_fetch(remote, NULL, &update_options.fetch_opts, NULL)) < 0 || + (error = git_object_lookup(&target_commit, sub_repo, git_submodule_index_id(sm), GIT_OBJ_COMMIT)) < 0) + goto done; + } + + if ((error = git_checkout_tree(sub_repo, target_commit, &update_options.checkout_opts)) != 0 || (error = git_repository_set_head_detached(sub_repo, git_submodule_index_id(sm))) < 0) goto done; diff --git a/src/sysdir.c b/src/sysdir.c index 29e53e239..e89db7697 100644 --- a/src/sysdir.c +++ b/src/sysdir.c @@ -171,7 +171,7 @@ int git_sysdir_set(git_sysdir_t which, const char *search_path) expand_path = strstr(search_path, PATH_MAGIC); /* reset the default if this path has been cleared */ - if (!search_path || expand_path) + if (!search_path) git_sysdir__dirs[which].guess(&git_sysdir__dirs[which].buf); /* if $PATH is not referenced, then just set the path */ diff --git a/src/transport.c b/src/transport.c index 327052fa3..f08d2dc68 100644 --- a/src/transport.c +++ b/src/transport.c @@ -29,9 +29,7 @@ static transport_definition local_transport_definition = { "file://", git_transp static transport_definition transports[] = { { "git://", git_transport_smart, &git_subtransport_definition }, { "http://", git_transport_smart, &http_subtransport_definition }, -#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) { "https://", git_transport_smart, &http_subtransport_definition }, -#endif { "file://", git_transport_local, NULL }, #ifdef GIT_SSH { "ssh://", git_transport_smart, &ssh_subtransport_definition }, @@ -89,10 +87,10 @@ static int transport_find_fn( /* For other systems, perform the SSH check first, to avoid going to the * filesystem if it is not necessary */ - /* It could be a SSH remote path. Check to see if there's a : - * SSH is an unsupported transport mechanism in this version of libgit2 */ + /* It could be a SSH remote path. Check to see if there's a : */ if (!definition && strrchr(url, ':')) { - // re-search transports again with ssh:// as url so that we can find a third party ssh transport + /* re-search transports again with ssh:// as url + * so that we can find a third party ssh transport */ definition = transport_find_by_url("ssh://"); } diff --git a/src/transports/http.c b/src/transports/http.c index f0efd956a..155fd7b30 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -208,7 +208,7 @@ static int gen_request( git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); - git_buf_printf(buf, "User-Agent: git/1.0 (%s)\r\n", user_agent()); + git_buf_printf(buf, "User-Agent: git/2.0 (%s)\r\n", user_agent()); git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host); if (s->chunked || content_length > 0) { @@ -555,10 +555,40 @@ static int write_chunk(git_stream *io, const char *buffer, size_t len) return 0; } +static int apply_proxy_config(http_subtransport *t) +{ + int error; + git_proxy_t proxy_type; + + if (!git_stream_supports_proxy(t->io)) + return 0; + + proxy_type = t->owner->proxy.type; + + if (proxy_type == GIT_PROXY_NONE) + return 0; + + if (proxy_type == GIT_PROXY_AUTO) { + char *url; + git_proxy_options opts = GIT_PROXY_OPTIONS_INIT; + + if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0) + return error; + + opts.type = GIT_PROXY_SPECIFIED; + opts.url = url; + error = git_stream_set_proxy(t->io, &opts); + git__free(url); + + return error; + } + + return git_stream_set_proxy(t->io, &t->owner->proxy); +} + static int http_connect(http_subtransport *t) { int error; - char *proxy_url; if (t->connected && http_should_keep_alive(&t->parser) && @@ -587,28 +617,19 @@ static int http_connect(http_subtransport *t) GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream"); - if (git_stream_supports_proxy(t->io) && - !git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url)) { - error = git_stream_set_proxy(t->io, proxy_url); - git__free(proxy_url); - - if (error < 0) - return error; - } + apply_proxy_config(t); error = git_stream_connect(t->io); -#if defined(GIT_OPENSSL) || defined(GIT_SECURE_TRANSPORT) || defined(GIT_CURL) if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL && git_stream_is_encrypted(t->io)) { git_cert *cert; - int is_valid; + int is_valid = (error == GIT_OK); if ((error = git_stream_certificate(&cert, t->io)) < 0) return error; giterr_clear(); - is_valid = error != GIT_ECERTIFICATE; error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload); if (error < 0) { @@ -618,7 +639,7 @@ static int http_connect(http_subtransport *t) return error; } } -#endif + if (error < 0) return error; @@ -1014,6 +1035,8 @@ static int http_close(git_smart_subtransport *subtransport) clear_parser_state(t); + t->connected = 0; + if (t->io) { git_stream_close(t->io); git_stream_free(t->io); diff --git a/src/transports/local.c b/src/transports/local.c index 1c6e5f01e..4eae9dead 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -25,6 +25,7 @@ #include "odb.h" #include "push.h" #include "remote.h" +#include "proxy.h" typedef struct { git_transport parent; @@ -199,6 +200,7 @@ static int local_connect( const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, + const git_proxy_options *proxy, int direction, int flags) { git_repository *repo; @@ -209,6 +211,7 @@ static int local_connect( GIT_UNUSED(cred_acquire_cb); GIT_UNUSED(cred_acquire_payload); + GIT_UNUSED(proxy); if (t->connected) return 0; @@ -439,7 +442,7 @@ static int local_push( if (!url || t->parent.close(&t->parent) < 0 || t->parent.connect(&t->parent, url, - NULL, NULL, GIT_DIRECTION_PUSH, flags)) + NULL, NULL, NULL, GIT_DIRECTION_PUSH, flags)) goto on_error; } diff --git a/src/transports/smart.c b/src/transports/smart.c index b0611c35e..7a35c39d8 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -8,6 +8,7 @@ #include "smart.h" #include "refs.h" #include "refspec.h" +#include "proxy.h" static int git_smart__recv_cb(gitno_buffer *buf) { @@ -199,6 +200,7 @@ static int git_smart__connect( const char *url, git_cred_acquire_cb cred_acquire_cb, void *cred_acquire_payload, + const git_proxy_options *proxy, int direction, int flags) { @@ -216,6 +218,9 @@ static int git_smart__connect( t->url = git__strdup(url); GITERR_CHECK_ALLOC(t->url); + if (git_proxy_options_dup(&t->proxy, proxy) < 0) + return -1; + t->direction = direction; t->flags = flags; t->cred_acquire_cb = cred_acquire_cb; @@ -439,6 +444,7 @@ static void git_smart__free(git_transport *transport) git_pkt_free(p); git_vector_free(refs); + git__free((char *)t->proxy.url); git_strarray_free(&t->custom_headers); diff --git a/src/transports/smart.h b/src/transports/smart.h index 800466adf..0a0c3fc1b 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -133,6 +133,7 @@ typedef struct { char *url; git_cred_acquire_cb cred_acquire_cb; void *cred_acquire_payload; + git_proxy_options proxy; int direction; int flags; git_transport_message_cb progress_cb; diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 2297cc94f..e05196cd8 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -427,15 +427,23 @@ int git_pkt_parse_line( if (bufflen > 0 && bufflen < (size_t)len) return GIT_EBUFS; + /* + * The length has to be exactly 0 in case of a flush + * packet or greater than PKT_LEN_SIZE, as the decoded + * length includes its own encoded length of four bytes. + */ + if (len != 0 && len < PKT_LEN_SIZE) + return GIT_ERROR; + line += PKT_LEN_SIZE; /* - * TODO: How do we deal with empty lines? Try again? with the next - * line? + * The Git protocol does not specify empty lines as part + * of the protocol. Not knowing what to do with an empty + * line, we should return an error upon hitting one. */ if (len == PKT_LEN_SIZE) { - *head = NULL; - *out = line; - return 0; + giterr_set_str(GITERR_NET, "Invalid empty packet"); + return GIT_ERROR; } if (len == 0) { /* Flush pkt */ diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 3448fa7fb..db6a8b9c8 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -50,7 +50,7 @@ int git_smart__store_refs(transport_smart *t, int flushes) if ((recvd = gitno_recv(buf)) < 0) return recvd; - if (recvd == 0 && !flush) { + if (recvd == 0) { giterr_set(GITERR_NET, "early EOF"); return GIT_EEOF; } @@ -222,8 +222,12 @@ static int recv_pkt(git_pkt **out, gitno_buffer *buf) if (error < 0 && error != GIT_EBUFS) return error; - if ((ret = gitno_recv(buf)) < 0) + if ((ret = gitno_recv(buf)) < 0) { return ret; + } else if (ret == 0) { + giterr_set(GITERR_NET, "early EOF"); + return GIT_EEOF; + } } while (error); gitno_consume(buf, line_end); @@ -408,12 +412,12 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c if (i % 20 == 0 && t->rpc) { git_pkt_ack *pkt; - unsigned int i; + unsigned int j; if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) goto on_error; - git_vector_foreach(&t->common, i, pkt) { + git_vector_foreach(&t->common, j, pkt) { if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) goto on_error; } @@ -428,12 +432,12 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c /* Tell the other end that we're done negotiating */ if (t->rpc && t->common.length > 0) { git_pkt_ack *pkt; - unsigned int i; + unsigned int j; if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0) goto on_error; - git_vector_foreach(&t->common, i, pkt) { + git_vector_foreach(&t->common, j, pkt) { if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) goto on_error; } @@ -724,7 +728,7 @@ static int add_push_report_pkt(git_push *push, git_pkt *pkt) static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, git_buf *data_pkt_buf) { git_pkt *pkt; - const char *line, *line_end; + const char *line, *line_end = NULL; size_t line_len; int error; int reading_from_buf = data_pkt_buf->size > 0; @@ -759,14 +763,6 @@ static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt, line_len -= (line_end - line); line = line_end; - /* When a valid packet with no content has been - * read, git_pkt_parse_line does not report an - * error, but the pkt pointer has not been set. - * Handle this by skipping over empty packets. - */ - if (pkt == NULL) - continue; - error = add_push_report_pkt(push, pkt); git_pkt_free(pkt); @@ -821,9 +817,6 @@ static int parse_report(transport_smart *transport, git_push *push) error = 0; - if (pkt == NULL) - continue; - switch (pkt->type) { case GIT_PKT_DATA: /* This is a sideband packet which contains other packets */ diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 32b838084..78e42cf3b 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -91,13 +91,39 @@ typedef struct { git_smart_subtransport parent; transport_smart *owner; gitno_connection_data connection_data; + gitno_connection_data proxy_connection_data; git_cred *cred; git_cred *url_cred; + git_cred *proxy_cred; int auth_mechanism; HINTERNET session; HINTERNET connection; } winhttp_subtransport; +static int apply_basic_credential_proxy(HINTERNET request, git_cred *cred) +{ + git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; + wchar_t *user, *pass; + int error; + + if ((error = git__utf8_to_16_alloc(&user, c->username)) < 0) + return error; + + if ((error = git__utf8_to_16_alloc(&pass, c->password)) < 0) + return error; + + if (!WinHttpSetCredentials(request, WINHTTP_AUTH_TARGET_PROXY, WINHTTP_AUTH_SCHEME_BASIC, + user, pass, NULL)) { + giterr_set(GITERR_OS, "failed to set proxy auth"); + error = -1; + } + + git__free(user); + git__free(pass); + + return error; +} + static int apply_basic_credential(HINTERNET request, git_cred *cred) { git_cred_userpass_plaintext *c = (git_cred_userpass_plaintext *)cred; @@ -271,6 +297,37 @@ static void winhttp_stream_close(winhttp_stream *s) s->sent_request = 0; } +/** + * Extract the url and password from a URL. The outputs are pointers + * into the input. + */ +static int userpass_from_url(wchar_t **user, int *user_len, + wchar_t **pass, int *pass_len, + const wchar_t *url, int url_len) +{ + URL_COMPONENTS components = { 0 }; + + components.dwStructSize = sizeof(components); + /* These tell WinHttpCrackUrl that we're interested in the fields */ + components.dwUserNameLength = 1; + components.dwPasswordLength = 1; + + if (!WinHttpCrackUrl(url, url_len, 0, &components)) { + giterr_set(GITERR_OS, "failed to extract user/pass from url"); + return -1; + } + + *user = components.lpszUserName; + *user_len = components.dwUserNameLength; + *pass = components.lpszPassword; + *pass_len = components.dwPasswordLength; + + return 0; +} + +#define SCHEME_HTTP "http://" +#define SCHEME_HTTPS "https://" + static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); @@ -284,6 +341,7 @@ static int winhttp_stream_connect(winhttp_stream *s) int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; size_t i; + const git_proxy_options *proxy_opts; /* Prepare URL */ git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); @@ -317,26 +375,66 @@ static int winhttp_stream_connect(winhttp_stream *s) goto on_error; } - /* Set proxy if necessary */ - if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0) - goto on_error; + proxy_opts = &t->owner->proxy; + if (proxy_opts->type == GIT_PROXY_AUTO) { + /* Set proxy if necessary */ + if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0) + goto on_error; + } + else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { + proxy_url = git__strdup(proxy_opts->url); + GITERR_CHECK_ALLOC(proxy_url); + } if (proxy_url) { + git_buf processed_url = GIT_BUF_INIT; WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; - /* Convert URL to wide characters */ - int proxy_wide_len = git__utf8_to_16_alloc(&proxy_wide, proxy_url); + if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) { + t->proxy_connection_data.use_ssl = false; + } else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) { + t->proxy_connection_data.use_ssl = true; + } else { + giterr_set(GITERR_NET, "invalid URL: '%s'", proxy_url); + return -1; + } - if (proxy_wide_len < 0) { - giterr_set(GITERR_OS, "Failed to convert string to wide form"); + gitno_connection_data_free_ptrs(&t->proxy_connection_data); + + if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL, + &t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0) + goto on_error; + + if (t->proxy_connection_data.user && t->proxy_connection_data.pass) { + if (t->proxy_cred) { + t->proxy_cred->free(t->proxy_cred); + } + + if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0) + goto on_error; + } + + if (t->proxy_connection_data.use_ssl) + git_buf_PUTS(&processed_url, SCHEME_HTTPS); + else + git_buf_PUTS(&processed_url, SCHEME_HTTP); + + git_buf_puts(&processed_url, t->proxy_connection_data.host); + if (t->proxy_connection_data.port) + git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port); + + if (git_buf_oom(&processed_url)) { + giterr_set_oom(); + error = -1; goto on_error; } - /* Strip any trailing forward slash on the proxy URL; - * WinHTTP doesn't like it if one is present */ - if (proxy_wide_len > 1 && L'/' == proxy_wide[proxy_wide_len - 2]) - proxy_wide[proxy_wide_len - 2] = L'\0'; + /* Convert URL to wide characters */ + error = git__utf8_to_16_alloc(&proxy_wide, processed_url.ptr); + git_buf_free(&processed_url); + if (error < 0) + goto on_error; proxy_info.dwAccessType = WINHTTP_ACCESS_TYPE_NAMED_PROXY; proxy_info.lpszProxy = proxy_wide; @@ -352,6 +450,14 @@ static int winhttp_stream_connect(winhttp_stream *s) } git__free(proxy_wide); + + if (t->proxy_cred) { + if (t->proxy_cred->credtype == GIT_CREDTYPE_USERPASS_PLAINTEXT) { + if ((error = apply_basic_credential_proxy(s->request, t->proxy_cred)) < 0) + goto on_error; + } + } + } /* Disable WinHTTP redirects so we can handle them manually. Why, you ask? @@ -919,6 +1025,26 @@ replay: goto replay; } + /* Handle proxy authentication failures */ + if (status_code == HTTP_STATUS_PROXY_AUTH_REQ) { + int allowed_types; + + if (parse_unauthorized_response(s->request, &allowed_types, &t->auth_mechanism) < 0) + return -1; + + /* TODO: extract the username from the url, no payload? */ + if (t->owner->proxy.credentials) { + int cred_error = 1; + cred_error = t->owner->proxy.credentials(&t->proxy_cred, t->owner->proxy.url, NULL, allowed_types, NULL); + + if (cred_error < 0) + return cred_error; + } + + winhttp_stream_close(s); + goto replay; + } + /* Handle authentication failures */ if (HTTP_STATUS_DENIED == status_code && get_verb == s->verb) { int allowed_types; @@ -1362,12 +1488,19 @@ static int winhttp_close(git_smart_subtransport *subtransport) gitno_connection_data_free_ptrs(&t->connection_data); memset(&t->connection_data, 0x0, sizeof(gitno_connection_data)); + gitno_connection_data_free_ptrs(&t->proxy_connection_data); + memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data)); if (t->cred) { t->cred->free(t->cred); t->cred = NULL; } + if (t->proxy_cred) { + t->proxy_cred->free(t->proxy_cred); + t->proxy_cred = NULL; + } + if (t->url_cred) { t->url_cred->free(t->url_cred); t->url_cred = NULL; diff --git a/src/tree.c b/src/tree.c index 7cc415a09..9655ad739 100644 --- a/src/tree.c +++ b/src/tree.c @@ -917,7 +917,7 @@ int git_tree_entry_bypath( if (entry == NULL) { giterr_set(GITERR_TREE, - "the path '%.*s' does not exist in the given tree", filename_len, path); + "the path '%.*s' does not exist in the given tree", (int) filename_len, path); return GIT_ENOTFOUND; } @@ -927,7 +927,7 @@ int git_tree_entry_bypath( * then this entry *must* be a tree */ if (!git_tree_entry__is_tree(entry)) { giterr_set(GITERR_TREE, - "the path '%.*s' exists but is not a tree", filename_len, path); + "the path '%.*s' exists but is not a tree", (int) filename_len, path); return GIT_ENOTFOUND; } @@ -1039,3 +1039,268 @@ int git_tree_walk( return error; } +static int compare_entries(const void *_a, const void *_b) +{ + const git_tree_update *a = (git_tree_update *) _a; + const git_tree_update *b = (git_tree_update *) _b; + + return strcmp(a->path, b->path); +} + +static int on_dup_entry(void **old, void *new) +{ + GIT_UNUSED(old); GIT_UNUSED(new); + + giterr_set(GITERR_TREE, "duplicate entries given for update"); + return -1; +} + +/* + * We keep the previous tree and the new one at each level of the + * stack. When we leave a level we're done with that tree and we can + * write it out to the odb. + */ +typedef struct { + git_treebuilder *bld; + git_tree *tree; + char *name; +} tree_stack_entry; + +/** Count how many slashes (i.e. path components) there are in this string */ +GIT_INLINE(size_t) count_slashes(const char *path) +{ + size_t count = 0; + const char *slash; + + while ((slash = strchr(path, '/')) != NULL) { + count++; + path = slash + 1; + } + + return count; +} + +static bool next_component(git_buf *out, const char *in) +{ + const char *slash = strchr(in, '/'); + + git_buf_clear(out); + + if (slash) + git_buf_put(out, in, slash - in); + + return !!slash; +} + +static int create_popped_tree(tree_stack_entry *current, tree_stack_entry *popped, git_buf *component) +{ + int error; + git_oid new_tree; + + git_tree_free(popped->tree); + + /* If the tree would be empty, remove it from the one higher up */ + if (git_treebuilder_entrycount(popped->bld) == 0) { + git_treebuilder_free(popped->bld); + error = git_treebuilder_remove(current->bld, popped->name); + git__free(popped->name); + return error; + } + + error = git_treebuilder_write(&new_tree, popped->bld); + git_treebuilder_free(popped->bld); + + if (error < 0) { + git__free(popped->name); + return error; + } + + /* We've written out the tree, now we have to put the new value into its parent */ + git_buf_clear(component); + git_buf_puts(component, popped->name); + git__free(popped->name); + + GITERR_CHECK_ALLOC(component->ptr); + + /* Error out if this would create a D/F conflict in this update */ + if (current->tree) { + const git_tree_entry *to_replace; + to_replace = git_tree_entry_byname(current->tree, component->ptr); + if (to_replace && git_tree_entry_type(to_replace) != GIT_OBJ_TREE) { + giterr_set(GITERR_TREE, "D/F conflict when updating tree"); + return -1; + } + } + + return git_treebuilder_insert(NULL, current->bld, component->ptr, &new_tree, GIT_FILEMODE_TREE); +} + +int git_tree_create_updated(git_oid *out, git_repository *repo, git_tree *baseline, size_t nupdates, const git_tree_update *updates) +{ + git_array_t(tree_stack_entry) stack = GIT_ARRAY_INIT; + tree_stack_entry *root_elem; + git_vector entries; + int error; + size_t i; + git_buf component = GIT_BUF_INIT; + + if ((error = git_vector_init(&entries, nupdates, compare_entries)) < 0) + return error; + + /* Sort the entries for treversal */ + for (i = 0 ; i < nupdates; i++) { + if ((error = git_vector_insert_sorted(&entries, (void *) &updates[i], on_dup_entry)) < 0) + goto cleanup; + } + + root_elem = git_array_alloc(stack); + GITERR_CHECK_ALLOC(root_elem); + memset(root_elem, 0, sizeof(*root_elem)); + + if (baseline && (error = git_tree_dup(&root_elem->tree, baseline)) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&root_elem->bld, repo, root_elem->tree)) < 0) + goto cleanup; + + for (i = 0; i < nupdates; i++) { + const git_tree_update *last_update = i == 0 ? NULL : git_vector_get(&entries, i-1); + const git_tree_update *update = git_vector_get(&entries, i); + size_t common_prefix = 0, steps_up, j; + const char *path; + + /* Figure out how much we need to change from the previous tree */ + if (last_update) + common_prefix = git_path_common_dirlen(last_update->path, update->path); + + /* + * The entries are sorted, so when we find we're no + * longer in the same directory, we need to abandon + * the old tree (steps up) and dive down to the next + * one. + */ + steps_up = last_update == NULL ? 0 : count_slashes(&last_update->path[common_prefix]); + + for (j = 0; j < steps_up; j++) { + tree_stack_entry *current, *popped = git_array_pop(stack); + assert(popped); + + current = git_array_last(stack); + assert(current); + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Now that we've created the trees we popped from the stack, let's go back down */ + path = &update->path[common_prefix]; + while (next_component(&component, path)) { + tree_stack_entry *last, *new_entry; + const git_tree_entry *entry; + + last = git_array_last(stack); + entry = last->tree ? git_tree_entry_byname(last->tree, component.ptr) : NULL; + if (!entry) + entry = treebuilder_get(last->bld, component.ptr); + + if (entry && git_tree_entry_type(entry) != GIT_OBJ_TREE) { + giterr_set(GITERR_TREE, "D/F conflict when updating tree"); + error = -1; + goto cleanup; + } + + new_entry = git_array_alloc(stack); + GITERR_CHECK_ALLOC(new_entry); + memset(new_entry, 0, sizeof(*new_entry)); + + new_entry->tree = NULL; + if (entry && (error = git_tree_lookup(&new_entry->tree, repo, git_tree_entry_id(entry))) < 0) + goto cleanup; + + if ((error = git_treebuilder_new(&new_entry->bld, repo, new_entry->tree)) < 0) + goto cleanup; + + new_entry->name = git__strdup(component.ptr); + GITERR_CHECK_ALLOC(new_entry->name); + + /* Get to the start of the next component */ + path += component.size + 1; + } + + /* After all that, we're finally at the place where we want to perform the update */ + switch (update->action) { + case GIT_TREE_UPDATE_UPSERT: + { + /* Make sure we're replacing something of the same type */ + tree_stack_entry *last = git_array_last(stack); + char *basename = git_path_basename(update->path); + const git_tree_entry *e = git_treebuilder_get(last->bld, basename); + if (e && git_tree_entry_type(e) != git_object__type_from_filemode(update->filemode)) { + git__free(basename); + giterr_set(GITERR_TREE, "Cannot replace '%s' with '%s' at '%s'", + git_object_type2string(git_tree_entry_type(e)), + git_object_type2string(git_object__type_from_filemode(update->filemode)), + update->path); + error = -1; + goto cleanup; + } + + error = git_treebuilder_insert(NULL, last->bld, basename, &update->id, update->filemode); + git__free(basename); + break; + } + case GIT_TREE_UPDATE_REMOVE: + { + char *basename = git_path_basename(update->path); + error = git_treebuilder_remove(git_array_last(stack)->bld, basename); + git__free(basename); + break; + } + default: + giterr_set(GITERR_TREE, "unkown action for update"); + error = -1; + goto cleanup; + } + + if (error < 0) + goto cleanup; + } + + /* We're done, go up the stack again and write out the tree */ + { + tree_stack_entry *current = NULL, *popped = NULL; + while ((popped = git_array_pop(stack)) != NULL) { + current = git_array_last(stack); + /* We've reached the top, current is the root tree */ + if (!current) + break; + + if ((error = create_popped_tree(current, popped, &component)) < 0) + goto cleanup; + } + + /* Write out the root tree */ + git__free(popped->name); + git_tree_free(popped->tree); + + error = git_treebuilder_write(out, popped->bld); + git_treebuilder_free(popped->bld); + if (error < 0) + goto cleanup; + } + +cleanup: + { + tree_stack_entry *e; + while ((e = git_array_pop(stack)) != NULL) { + git_treebuilder_free(e->bld); + git_tree_free(e->tree); + git__free(e->name); + } + } + + git_buf_free(&component); + git_array_clear(stack); + git_vector_free(&entries); + return error; +} diff --git a/src/unix/posix.h b/src/unix/posix.h index 482d2c803..ad13291e8 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -80,4 +80,14 @@ GIT_INLINE(int) p_futimes(int f, const struct p_timeval t[2]) # define p_futimes futimes #endif +#ifdef HAVE_REGCOMP_L +#include +GIT_INLINE(int) p_regcomp(regex_t *preg, const char *pattern, int cflags) +{ + return regcomp_l(preg, pattern, cflags, (locale_t) 0); +} +#else +# define p_regcomp regcomp +#endif + #endif diff --git a/src/unix/pthread.h b/src/unix/pthread.h index 0f3f17927..3f23d10d5 100644 --- a/src/unix/pthread.h +++ b/src/unix/pthread.h @@ -17,6 +17,8 @@ typedef struct { pthread_create(&(git_thread_ptr)->thread, NULL, start_routine, arg) #define git_thread_join(git_thread_ptr, status) \ pthread_join((git_thread_ptr)->thread, status) +#define git_thread_currentid() ((size_t)(pthread_self())) +#define git_thread_exit(retval) pthread_exit(retval) /* Git Mutex */ #define git_mutex pthread_mutex_t diff --git a/src/util.c b/src/util.c index cc4b43241..76ca711b2 100644 --- a/src/util.c +++ b/src/util.c @@ -65,6 +65,12 @@ int git_strarray_copy(git_strarray *tgt, const git_strarray *src) } int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int base) +{ + + return git__strntol64(result, nptr, (size_t)-1, endptr, base); +} + +int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { const char *p; int64_t n, nn; @@ -111,7 +117,7 @@ int git__strtol64(int64_t *result, const char *nptr, const char **endptr, int ba /* * Non-empty sequence of digits */ - for (;; p++,ndig++) { + for (; nptr_len > 0; p++,ndig++,nptr_len--) { c = *p; v = base; if ('0'<=c && c<='9') @@ -147,12 +153,18 @@ Return: } int git__strtol32(int32_t *result, const char *nptr, const char **endptr, int base) +{ + + return git__strntol32(result, nptr, (size_t)-1, endptr, base); +} + +int git__strntol32(int32_t *result, const char *nptr, size_t nptr_len, const char **endptr, int base) { int error; int32_t tmp_int; int64_t tmp_long; - if ((error = git__strtol64(&tmp_long, nptr, endptr, base)) < 0) + if ((error = git__strntol64(&tmp_long, nptr, nptr_len, endptr, base)) < 0) return error; tmp_int = tmp_long & 0xFFFFFFFF; @@ -321,6 +333,12 @@ char *git__strsep(char **end, const char *sep) return NULL; } +size_t git__linenlen(const char *buffer, size_t buffer_len) +{ + char *nl = memchr(buffer, '\n', buffer_len); + return nl ? (size_t)(nl - buffer) + 1 : buffer_len; +} + void git__hexdump(const char *buffer, size_t len) { static const size_t LINE_WIDTH = 16; @@ -765,6 +783,11 @@ int git__utf8_iterate(const uint8_t *str, int str_len, int32_t *dst) return length; } +double git_time_monotonic(void) +{ + return git__timer(); +} + #ifdef GIT_WIN32 int git__getenv(git_buf *out, const char *name) { diff --git a/src/util.h b/src/util.h index d0c3cd04a..eb15250d8 100644 --- a/src/util.h +++ b/src/util.h @@ -263,7 +263,10 @@ GIT_INLINE(int) git__signum(int val) } extern int git__strtol32(int32_t *n, const char *buff, const char **end_buf, int base); +extern int git__strntol32(int32_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); extern int git__strtol64(int64_t *n, const char *buff, const char **end_buf, int base); +extern int git__strntol64(int64_t *n, const char *buff, size_t buff_len, const char **end_buf, int base); + extern void git__hexdump(const char *buffer, size_t n); extern uint32_t git__hash(const void *key, int len, uint32_t seed); @@ -290,6 +293,8 @@ GIT_INLINE(int) git__tolower(int c) # define git__tolower(a) tolower(a) #endif +extern size_t git__linenlen(const char *buffer, size_t buffer_len); + GIT_INLINE(const char *) git__next_line(const char *s) { while (*s && *s != '\n') s++; @@ -466,6 +471,11 @@ GIT_INLINE(bool) git__iswildcard(int c) return (c == '*' || c == '?' || c == '['); } +GIT_INLINE(bool) git__isxdigit(int c) +{ + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); +} + /* * Parse a string value as a boolean, just like Core Git does. * diff --git a/src/varint.c b/src/varint.c new file mode 100644 index 000000000..2f868607c --- /dev/null +++ b/src/varint.c @@ -0,0 +1,44 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "common.h" +#include "varint.h" + +uintmax_t git_decode_varint(const unsigned char *bufp, size_t *varint_len) +{ + const unsigned char *buf = bufp; + unsigned char c = *buf++; + uintmax_t val = c & 127; + while (c & 128) { + val += 1; + if (!val || MSB(val, 7)) { + /* This is not a valid varint_len, so it signals + the error */ + *varint_len = 0; + return 0; /* overflow */ + } + c = *buf++; + val = (val << 7) + (c & 127); + } + *varint_len = buf - bufp; + return val; +} + +int git_encode_varint(unsigned char *buf, size_t bufsize, uintmax_t value) +{ + unsigned char varint[16]; + unsigned pos = sizeof(varint) - 1; + varint[pos] = value & 127; + while (value >>= 7) + varint[--pos] = 128 | (--value & 127); + if (buf) { + if (bufsize < pos) + return -1; + memcpy(buf, varint + pos, sizeof(varint) - pos); + } + return sizeof(varint) - pos; +} diff --git a/src/varint.h b/src/varint.h new file mode 100644 index 000000000..650ec7d2a --- /dev/null +++ b/src/varint.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_varint_h__ +#define INCLUDE_varint_h__ + +#include + +extern int git_encode_varint(unsigned char *, size_t, uintmax_t); +extern uintmax_t git_decode_varint(const unsigned char *, size_t *); + +#endif diff --git a/src/vector.c b/src/vector.c index a81d463ef..baec8036f 100644 --- a/src/vector.c +++ b/src/vector.c @@ -7,6 +7,7 @@ #include "common.h" #include "vector.h" +#include "integer.h" /* In elements, not bytes */ #define MIN_ALLOCSIZE 8 @@ -330,6 +331,47 @@ int git_vector_resize_to(git_vector *v, size_t new_length) return 0; } +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len) +{ + size_t new_length; + + assert(insert_len > 0 && idx <= v->length); + + GITERR_CHECK_ALLOC_ADD(&new_length, v->length, insert_len); + + if (new_length > v->_alloc_size && resize_vector(v, new_length) < 0) + return -1; + + memmove(&v->contents[idx + insert_len], &v->contents[idx], + sizeof(void *) * (v->length - idx)); + memset(&v->contents[idx], 0, sizeof(void *) * insert_len); + + v->length = new_length; + return 0; +} + +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len) +{ + size_t new_length = v->length - remove_len; + size_t end_idx = 0; + + assert(remove_len > 0); + + if (git__add_sizet_overflow(&end_idx, idx, remove_len)) + assert(0); + + assert(end_idx <= v->length); + + if (end_idx < v->length) + memmove(&v->contents[idx], &v->contents[end_idx], + sizeof(void *) * (v->length - end_idx)); + + memset(&v->contents[new_length], 0, sizeof(void *) * remove_len); + + v->length = new_length; + return 0; +} + int git_vector_set(void **old, git_vector *v, size_t position, void *value) { if (position + 1 > v->length) { @@ -359,3 +401,19 @@ int git_vector_verify_sorted(const git_vector *v) return 0; } + +void git_vector_reverse(git_vector *v) +{ + size_t a, b; + + a = 0; + b = v->length - 1; + + while (a < b) { + void *tmp = v->contents[a]; + v->contents[a] = v->contents[b]; + v->contents[b] = tmp; + a++; + b--; + } +} diff --git a/src/vector.h b/src/vector.h index b7500ded3..cc4c314d5 100644 --- a/src/vector.h +++ b/src/vector.h @@ -93,6 +93,9 @@ void git_vector_remove_matching( void *payload); int git_vector_resize_to(git_vector *v, size_t new_length); +int git_vector_insert_null(git_vector *v, size_t idx, size_t insert_len); +int git_vector_remove_range(git_vector *v, size_t idx, size_t remove_len); + int git_vector_set(void **old, git_vector *v, size_t position, void *value); /** Check if vector is sorted */ @@ -115,4 +118,9 @@ GIT_INLINE(void) git_vector_set_cmp(git_vector *v, git_vector_cmp cmp) /* Just use this in tests, not for realz. returns -1 if not sorted */ int git_vector_verify_sorted(const git_vector *v); +/** + * Reverse the vector in-place. + */ +void git_vector_reverse(git_vector *v); + #endif diff --git a/src/win32/posix.h b/src/win32/posix.h index 5fab267c2..73705fb2b 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -57,4 +57,7 @@ extern int p_lstat_posixly(const char *filename, struct stat *buf); extern struct tm * p_localtime_r(const time_t *timer, struct tm *result); extern struct tm * p_gmtime_r(const time_t *timer, struct tm *result); +/* Use the bundled regcomp */ +#define p_regcomp regcomp + #endif diff --git a/src/win32/thread.c b/src/win32/thread.c index 80d56ce5d..87318c9d3 100644 --- a/src/win32/thread.c +++ b/src/win32/thread.c @@ -26,6 +26,9 @@ static DWORD WINAPI git_win32__threadproc(LPVOID lpParameter) { git_thread *thread = lpParameter; + /* Set the current thread for `git_thread_exit` */ + GIT_GLOBAL->current_thread = thread; + thread->result = thread->proc(thread->param); git__free_tls_data(); @@ -95,6 +98,21 @@ int git_thread_join( return 0; } +void git_thread_exit(void *value) +{ + assert(GIT_GLOBAL->current_thread); + GIT_GLOBAL->current_thread->result = value; + + git__free_tls_data(); + + ExitThread(CLEAN_THREAD_EXIT); +} + +size_t git_thread_currentid(void) +{ + return GetCurrentThreadId(); +} + int git_mutex_init(git_mutex *GIT_RESTRICT mutex) { InitializeCriticalSection(mutex); diff --git a/src/win32/thread.h b/src/win32/thread.h index 0d01822a6..7f4a2170f 100644 --- a/src/win32/thread.h +++ b/src/win32/thread.h @@ -41,6 +41,8 @@ int git_thread_create(git_thread *GIT_RESTRICT, void *(*) (void *), void *GIT_RESTRICT); int git_thread_join(git_thread *, void **); +size_t git_thread_currentid(void); +void git_thread_exit(void *); int git_mutex_init(git_mutex *GIT_RESTRICT mutex); int git_mutex_free(git_mutex *); diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 2e475e5e9..784a7a04c 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -174,7 +174,7 @@ GIT_INLINE(int) git_win32__file_attribute_to_stat( /* st_size gets the UTF-8 length of the target name, in bytes, * not counting the NULL terminator */ if ((st->st_size = git__utf16_to_8(NULL, 0, target)) < 0) { - giterr_set(GITERR_OS, "Could not convert reparse point name for '%s'", path); + giterr_set(GITERR_OS, "Could not convert reparse point name for '%ls'", path); return -1; } } diff --git a/src/zstream.c b/src/zstream.c index 2130bc3ca..d949aa81a 100644 --- a/src/zstream.c +++ b/src/zstream.c @@ -21,27 +21,38 @@ static int zstream_seterr(git_zstream *zs) if (zs->zerr == Z_MEM_ERROR) giterr_set_oom(); else if (zs->z.msg) - giterr_set(GITERR_ZLIB, zs->z.msg); + giterr_set_str(GITERR_ZLIB, zs->z.msg); else giterr_set(GITERR_ZLIB, "Unknown compression error"); return -1; } -int git_zstream_init(git_zstream *zstream) +int git_zstream_init(git_zstream *zstream, git_zstream_t type) { - zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); + zstream->type = type; + + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflateInit(&zstream->z); + else + zstream->zerr = deflateInit(&zstream->z, Z_DEFAULT_COMPRESSION); return zstream_seterr(zstream); } void git_zstream_free(git_zstream *zstream) { - deflateEnd(&zstream->z); + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateEnd(&zstream->z); + else + deflateEnd(&zstream->z); } void git_zstream_reset(git_zstream *zstream) { - deflateReset(&zstream->z); + if (zstream->type == GIT_ZSTREAM_INFLATE) + inflateReset(&zstream->z); + else + deflateReset(&zstream->z); zstream->in = NULL; zstream->in_len = 0; zstream->zerr = Z_STREAM_END; @@ -75,6 +86,11 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) int zflush = Z_FINISH; size_t out_remain = *out_len; + if (zstream->in_len && zstream->zerr == Z_STREAM_END) { + giterr_set(GITERR_ZLIB, "zlib input had trailing garbage"); + return -1; + } + while (out_remain > 0 && zstream->zerr != Z_STREAM_END) { size_t out_queued, in_queued, out_used, in_used; @@ -97,7 +113,10 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) out_queued = (size_t)zstream->z.avail_out; /* compress next chunk */ - zstream->zerr = deflate(&zstream->z, zflush); + if (zstream->type == GIT_ZSTREAM_INFLATE) + zstream->zerr = inflate(&zstream->z, zflush); + else + zstream->zerr = deflate(&zstream->z, zflush); if (zstream->zerr == Z_STREAM_ERROR) return zstream_seterr(zstream); @@ -120,12 +139,12 @@ int git_zstream_get_output(void *out, size_t *out_len, git_zstream *zstream) return 0; } -int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len) +static int zstream_buf(git_buf *out, const void *in, size_t in_len, git_zstream_t type) { git_zstream zs = GIT_ZSTREAM_INIT; int error = 0; - if ((error = git_zstream_init(&zs)) < 0) + if ((error = git_zstream_init(&zs, type)) < 0) return error; if ((error = git_zstream_set_input(&zs, in, in_len)) < 0) @@ -154,3 +173,13 @@ done: git_zstream_free(&zs); return error; } + +int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_DEFLATE); +} + +int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len) +{ + return zstream_buf(out, in, in_len, GIT_ZSTREAM_INFLATE); +} diff --git a/src/zstream.h b/src/zstream.h index 9b5bf6ace..f0006d32e 100644 --- a/src/zstream.h +++ b/src/zstream.h @@ -12,8 +12,14 @@ #include "common.h" #include "buffer.h" +typedef enum { + GIT_ZSTREAM_INFLATE, + GIT_ZSTREAM_DEFLATE, +} git_zstream_t; + typedef struct { z_stream z; + git_zstream_t type; const char *in; size_t in_len; int zerr; @@ -21,7 +27,7 @@ typedef struct { #define GIT_ZSTREAM_INIT {{0}} -int git_zstream_init(git_zstream *zstream); +int git_zstream_init(git_zstream *zstream, git_zstream_t type); void git_zstream_free(git_zstream *zstream); int git_zstream_set_input(git_zstream *zstream, const void *in, size_t in_len); @@ -35,5 +41,6 @@ bool git_zstream_done(git_zstream *zstream); void git_zstream_reset(git_zstream *zstream); int git_zstream_deflatebuf(git_buf *out, const void *in, size_t in_len); +int git_zstream_inflatebuf(git_buf *out, const void *in, size_t in_len); #endif /* INCLUDE_zstream_h__ */ diff --git a/tests/apply/fromdiff.c b/tests/apply/fromdiff.c new file mode 100644 index 000000000..349773964 --- /dev/null +++ b/tests/apply/fromdiff.c @@ -0,0 +1,289 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "repository.h" +#include "buf_text.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; +static git_diff_options binary_opts = GIT_DIFF_OPTIONS_INIT; + +void test_apply_fromdiff__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); + + binary_opts.flags |= GIT_DIFF_SHOW_BINARY; +} + +void test_apply_fromdiff__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_gitbuf( + const git_buf *old, + const char *oldname, + const git_buf *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_patch *patch; + git_buf result = GIT_BUF_INIT; + git_buf patchbuf = GIT_BUF_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffers(&patch, + old ? old->ptr : NULL, old ? old->size : 0, + oldname, + new ? new->ptr : NULL, new ? new->size : 0, + newname, + diff_opts)); + + if (patch_expected) { + cl_git_pass(git_patch_to_buf(&patchbuf, patch)); + cl_assert_equal_s(patch_expected, patchbuf.ptr); + } + + error = git_apply__patch(&result, &filename, &mode, old ? old->ptr : NULL, old ? old->size : 0, patch); + + if (error == 0 && new == NULL) { + cl_assert_equal_i(0, result.size); + cl_assert_equal_p(NULL, filename); + cl_assert_equal_i(0, mode); + } + else if (error == 0) { + cl_assert_equal_s(new->ptr, result.ptr); + cl_assert_equal_s(newname ? newname : oldname, filename); + cl_assert_equal_i(0100644, mode); + } + + git__free(filename); + git_buf_free(&result); + git_buf_free(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int apply_buf( + const char *old, + const char *oldname, + const char *new, + const char *newname, + const char *patch_expected, + const git_diff_options *diff_opts) +{ + git_buf o = GIT_BUF_INIT, n = GIT_BUF_INIT, + *optr = NULL, *nptr = NULL; + + if (old) { + o.ptr = (char *)old; + o.size = strlen(old); + optr = &o; + } + + if (new) { + n.ptr = (char *)new; + n.size = strlen(new); + nptr = &n; + } + + return apply_gitbuf(optr, oldname, nptr, newname, patch_expected, diff_opts); +} + +void test_apply_fromdiff__change_middle(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL)); +} + +void test_apply_fromdiff__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_MIDDLE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__change_firstline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_FIRSTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL)); +} + +void test_apply_fromdiff__lastline(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_CHANGE_LASTLINE, "file.txt", + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL)); +} + +void test_apply_fromdiff__prepend(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND, NULL)); +} + +void test_apply_fromdiff__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND, NULL)); +} + +void test_apply_fromdiff__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_APPEND, "file.txt", + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts)); +} + +void test_apply_fromdiff__prepend_and_append(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_PREPEND_AND_APPEND, "file.txt", + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL)); +} + +void test_apply_fromdiff__to_empty_file(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + "", NULL, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL)); +} + +void test_apply_fromdiff__from_empty_file(void) +{ + cl_git_pass(apply_buf( + "", NULL, + FILE_ORIGINAL, "file.txt", + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__add(void) +{ + cl_git_pass(apply_buf( + NULL, NULL, + FILE_ORIGINAL, "file.txt", + PATCH_ADD_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__delete(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + NULL, NULL, + PATCH_DELETE_ORIGINAL, NULL)); +} + +void test_apply_fromdiff__no_change(void) +{ + cl_git_pass(apply_buf( + FILE_ORIGINAL, "file.txt", + FILE_ORIGINAL, "file.txt", + "", NULL)); +} + +void test_apply_fromdiff__binary_add(void) +{ + git_buf newfile = GIT_BUF_INIT; + + newfile.ptr = FILE_BINARY_DELTA_MODIFIED; + newfile.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + NULL, NULL, + &newfile, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_no_change(void) +{ + git_buf original = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &original, "binary.bin", + "", &binary_opts)); +} + +void test_apply_fromdiff__binary_change_delta(void) +{ + git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_ORIGINAL; + original.size = FILE_BINARY_DELTA_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_DELTA_MODIFIED; + modified.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_change_literal(void) +{ + git_buf original = GIT_BUF_INIT, modified = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_LITERAL_ORIGINAL; + original.size = FILE_BINARY_LITERAL_ORIGINAL_LEN; + + modified.ptr = FILE_BINARY_LITERAL_MODIFIED; + modified.size = FILE_BINARY_LITERAL_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + &modified, "binary.bin", + NULL, &binary_opts)); +} + +void test_apply_fromdiff__binary_delete(void) +{ + git_buf original = GIT_BUF_INIT; + + original.ptr = FILE_BINARY_DELTA_MODIFIED; + original.size = FILE_BINARY_DELTA_MODIFIED_LEN; + + cl_git_pass(apply_gitbuf( + &original, "binary.bin", + NULL, NULL, + NULL, &binary_opts)); +} diff --git a/tests/apply/fromfile.c b/tests/apply/fromfile.c new file mode 100644 index 000000000..31fffa1a2 --- /dev/null +++ b/tests/apply/fromfile.c @@ -0,0 +1,449 @@ +#include "clar_libgit2.h" +#include "git2/sys/repository.h" + +#include "apply.h" +#include "patch.h" +#include "patch_parse.h" +#include "repository.h" +#include "buf_text.h" + +#include "../patch/patch_common.h" + +static git_repository *repo = NULL; + +void test_apply_fromfile__initialize(void) +{ + repo = cl_git_sandbox_init("renames"); +} + +void test_apply_fromfile__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +static int apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch; + git_buf result = GIT_BUF_INIT; + git_buf patchbuf = GIT_BUF_INIT; + char *filename; + unsigned int mode; + int error; + + cl_git_pass(git_patch_from_buffer(&patch, patchfile, strlen(patchfile), NULL)); + + error = git_apply__patch(&result, &filename, &mode, old, old_len, patch); + + if (error == 0) { + cl_assert_equal_i(new_len, result.size); + cl_assert(memcmp(new, result.ptr, new_len) == 0); + + cl_assert_equal_s(filename_expected, filename); + cl_assert_equal_i(mode_expected, mode); + } + + git__free(filename); + git_buf_free(&result); + git_buf_free(&patchbuf); + git_patch_free(patch); + + return error; +} + +static int validate_and_apply_patchfile( + const char *old, + size_t old_len, + const char *new, + size_t new_len, + const char *patchfile, + const git_diff_options *diff_opts, + const char *filename_expected, + unsigned int mode_expected) +{ + git_patch *patch_fromdiff; + git_buf validated = GIT_BUF_INIT; + int error; + + cl_git_pass(git_patch_from_buffers(&patch_fromdiff, + old, old_len, "file.txt", + new, new_len, "file.txt", + diff_opts)); + cl_git_pass(git_patch_to_buf(&validated, patch_fromdiff)); + + cl_assert_equal_s(patchfile, validated.ptr); + + error = apply_patchfile(old, old_len, new, new_len, patchfile, filename_expected, mode_expected); + + git_buf_free(&validated); + git_patch_free(patch_fromdiff); + + return error; +} + +void test_apply_fromfile__change_middle(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, + &diff_opts, "file.txt", 0100644)); +} + + +void test_apply_fromfile__change_firstline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_FIRSTLINE, strlen(FILE_CHANGE_FIRSTLINE), + PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__lastline(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_LASTLINE, strlen(FILE_CHANGE_LASTLINE), + PATCH_ORIGINAL_TO_CHANGE_LASTLINE, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_shrink_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_SHRINK, strlen(FILE_CHANGE_MIDDLE_SHRINK), + PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__change_middle_grow_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE_GROW, strlen(FILE_CHANGE_MIDDLE_GROW), + PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND, strlen(FILE_PREPEND), + PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__append_nocontext(void) +{ + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + diff_opts.context_lines = 0; + + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND, strlen(FILE_APPEND), + PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, &diff_opts, + "file.txt", 0100644)); +} + +void test_apply_fromfile__prepend_and_append(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_PREPEND_AND_APPEND, strlen(FILE_PREPEND_AND_APPEND), + PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, NULL, + "file.txt", 0100644)); +} + +void test_apply_fromfile__to_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + "", 0, + PATCH_ORIGINAL_TO_EMPTY_FILE, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__from_empty_file(void) +{ + cl_git_pass(validate_and_apply_patchfile( + "", 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_EMPTY_FILE_TO_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__add(void) +{ + cl_git_pass(validate_and_apply_patchfile( + NULL, 0, + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_ADD_ORIGINAL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__delete(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + NULL, 0, + PATCH_DELETE_ORIGINAL, NULL, NULL, 0)); +} + + +void test_apply_fromfile__rename_exact(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_RENAME_EXACT, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR, "newfile.txt", 0100644)); +} + +void test_apply_fromfile__rename_similar_quotedname(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_RENAME_SIMILAR_QUOTEDNAME, "foo\"bar.txt", 0100644)); +} + +void test_apply_fromfile__modechange(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + PATCH_MODECHANGE_UNCHANGED, "file.txt", 0100755)); +} + +void test_apply_fromfile__modechange_with_modification(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_MODECHANGE_MODIFIED, "file.txt", 0100755)); +} + +void test_apply_fromfile__noisy(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY, "file.txt", 0100644)); +} + +void test_apply_fromfile__noisy_nocontext(void) +{ + cl_git_pass(apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_CHANGE_MIDDLE, strlen(FILE_CHANGE_MIDDLE), + PATCH_NOISY_NOCONTEXT, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_truncated_1(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_1, + strlen(PATCH_TRUNCATED_1), NULL)); +} + +void test_apply_fromfile__fail_truncated_2(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_2, + strlen(PATCH_TRUNCATED_2), NULL)); +} + +void test_apply_fromfile__fail_truncated_3(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_TRUNCATED_3, + strlen(PATCH_TRUNCATED_3), NULL)); +} + +void test_apply_fromfile__fail_corrupt_githeader(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, + strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); +} + +void test_apply_fromfile__empty_context(void) +{ + cl_git_pass(apply_patchfile( + FILE_EMPTY_CONTEXT_ORIGINAL, strlen(FILE_EMPTY_CONTEXT_ORIGINAL), + FILE_EMPTY_CONTEXT_MODIFIED, strlen(FILE_EMPTY_CONTEXT_MODIFIED), + PATCH_EMPTY_CONTEXT, + "file.txt", 0100644)); +} + +void test_apply_fromfile__append_no_nl(void) +{ + cl_git_pass(validate_and_apply_patchfile( + FILE_ORIGINAL, strlen(FILE_ORIGINAL), + FILE_APPEND_NO_NL, strlen(FILE_APPEND_NO_NL), + PATCH_APPEND_NO_NL, NULL, "file.txt", 0100644)); +} + +void test_apply_fromfile__fail_missing_new_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_NEW_FILE, + strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); +} + +void test_apply_fromfile__fail_missing_old_file(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_OLD_FILE, + strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); +} + +void test_apply_fromfile__fail_no_changes(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_NO_CHANGES, + strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); +} + +void test_apply_fromfile__fail_missing_hunk_header(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_HUNK_HEADER, + strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); +} + +void test_apply_fromfile__fail_not_a_patch(void) +{ + git_patch *patch; + cl_git_fail(git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, + strlen(PATCH_NOT_A_PATCH), NULL)); +} + +void test_apply_fromfile__binary_add(void) +{ + cl_git_pass(apply_patchfile( + NULL, 0, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_ADD, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_delta(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_literal(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_LITERAL_ORIGINAL, FILE_BINARY_LITERAL_ORIGINAL_LEN, + FILE_BINARY_LITERAL_MODIFIED, FILE_BINARY_LITERAL_MODIFIED_LEN, + PATCH_BINARY_LITERAL, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_delete(void) +{ + cl_git_pass(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_DELETE, NULL, 0)); +} + +void test_apply_fromfile__binary_change_does_not_apply(void) +{ + /* try to apply patch backwards, ensure it does not apply */ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + FILE_BINARY_DELTA_ORIGINAL, FILE_BINARY_DELTA_ORIGINAL_LEN, + PATCH_BINARY_DELTA, "binary.bin", 0100644)); +} + +void test_apply_fromfile__binary_change_must_be_reversible(void) +{ + cl_git_fail(apply_patchfile( + FILE_BINARY_DELTA_MODIFIED, FILE_BINARY_DELTA_MODIFIED_LEN, + NULL, 0, + PATCH_BINARY_NOT_REVERSIBLE, NULL, 0)); +} + +void test_apply_fromfile__empty_file_not_allowed(void) +{ + git_patch *patch; + + cl_git_fail(git_patch_from_buffer(&patch, "", 0, NULL)); + cl_git_fail(git_patch_from_buffer(&patch, NULL, 0, NULL)); +} diff --git a/tests/attr/ignore.c b/tests/attr/ignore.c index 91bf984a1..f1fe1c71f 100644 --- a/tests/attr/ignore.c +++ b/tests/attr/ignore.c @@ -132,6 +132,32 @@ void test_attr_ignore__leading_stars(void) assert_is_ignored(false, "dir1/kid2/file"); } +void test_attr_ignore__globs_and_path_delimiters(void) +{ + cl_git_rewritefile("attr/.gitignore", "foo/bar/**"); + assert_is_ignored(true, "foo/bar/baz"); + assert_is_ignored(true, "foo/bar/baz/quux"); + + cl_git_rewritefile("attr/.gitignore", "_*/"); + assert_is_ignored(true, "sub/_test/a/file"); + assert_is_ignored(false, "test_folder/file"); + assert_is_ignored(true, "_test/file"); + assert_is_ignored(true, "_test/a/file"); + + cl_git_rewritefile("attr/.gitignore", "**/_*/"); + assert_is_ignored(true, "sub/_test/a/file"); + assert_is_ignored(false, "test_folder/file"); + assert_is_ignored(true, "_test/file"); + assert_is_ignored(true, "_test/a/file"); + + cl_git_rewritefile("attr/.gitignore", "**/_*/foo/bar/*ux"); + + assert_is_ignored(true, "sub/_test/foo/bar/qux/file"); + assert_is_ignored(true, "_test/foo/bar/qux/file"); + assert_is_ignored(true, "_test/foo/bar/crux/file"); + assert_is_ignored(false, "_test/foo/bar/code/file"); +} + void test_attr_ignore__skip_gitignore_directory(void) { cl_git_rewritefile("attr/.git/info/exclude", "/NewFolder\n/NewFolder/NewFolder"); diff --git a/tests/buf/quote.c b/tests/buf/quote.c new file mode 100644 index 000000000..6f77ab9c1 --- /dev/null +++ b/tests/buf/quote.c @@ -0,0 +1,88 @@ +#include "clar_libgit2.h" +#include "buffer.h" + +static void expect_quote_pass(const char *expected, const char *str) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_puts(&buf, str)); + cl_git_pass(git_buf_quote(&buf)); + + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_buf_len(&buf)); + + git_buf_free(&buf); +} + +void test_buf_quote__quote_succeeds(void) +{ + expect_quote_pass("", ""); + expect_quote_pass("foo", "foo"); + expect_quote_pass("foo/bar/baz.c", "foo/bar/baz.c"); + expect_quote_pass("foo bar", "foo bar"); + expect_quote_pass("\"\\\"leading quote\"", "\"leading quote"); + expect_quote_pass("\"slash\\\\y\"", "slash\\y"); + expect_quote_pass("\"foo\\r\\nbar\"", "foo\r\nbar"); + expect_quote_pass("\"foo\\177bar\"", "foo\177bar"); + expect_quote_pass("\"foo\\001bar\"", "foo\001bar"); + expect_quote_pass("\"foo\\377bar\"", "foo\377bar"); +} + +static void expect_unquote_pass(const char *expected, const char *quoted) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_puts(&buf, quoted)); + cl_git_pass(git_buf_unquote(&buf)); + + cl_assert_equal_s(expected, git_buf_cstr(&buf)); + cl_assert_equal_i(strlen(expected), git_buf_len(&buf)); + + git_buf_free(&buf); +} + +static void expect_unquote_fail(const char *quoted) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_puts(&buf, quoted)); + cl_git_fail(git_buf_unquote(&buf)); + + git_buf_free(&buf); +} + +void test_buf_quote__unquote_succeeds(void) +{ + expect_unquote_pass("", "\"\""); + expect_unquote_pass(" ", "\" \""); + expect_unquote_pass("foo", "\"foo\""); + expect_unquote_pass("foo bar", "\"foo bar\""); + expect_unquote_pass("foo\"bar", "\"foo\\\"bar\""); + expect_unquote_pass("foo\\bar", "\"foo\\\\bar\""); + expect_unquote_pass("foo\tbar", "\"foo\\tbar\""); + expect_unquote_pass("\vfoo\tbar\n", "\"\\vfoo\\tbar\\n\""); + expect_unquote_pass("foo\nbar", "\"foo\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"foo\\015\\012bar\""); + expect_unquote_pass("foo\r\nbar", "\"\\146\\157\\157\\015\\012\\142\\141\\162\""); + expect_unquote_pass("newline: \n", "\"newline: \\012\""); + expect_unquote_pass("0xff: \377", "\"0xff: \\377\""); +} + +void test_buf_quote__unquote_fails(void) +{ + expect_unquote_fail("no quotes at all"); + expect_unquote_fail("\"no trailing quote"); + expect_unquote_fail("no leading quote\""); + expect_unquote_fail("\"invalid \\z escape char\""); + expect_unquote_fail("\"\\q invalid escape char\""); + expect_unquote_fail("\"invalid escape char \\p\""); + expect_unquote_fail("\"invalid \\1 escape char \""); + expect_unquote_fail("\"invalid \\14 escape char \""); + expect_unquote_fail("\"invalid \\280 escape char\""); + expect_unquote_fail("\"invalid \\378 escape char\""); + expect_unquote_fail("\"invalid \\380 escape char\""); + expect_unquote_fail("\"invalid \\411 escape char\""); + expect_unquote_fail("\"truncated escape char \\\""); + expect_unquote_fail("\"truncated escape char \\0\""); + expect_unquote_fail("\"truncated escape char \\01\""); +} diff --git a/tests/clar.c b/tests/clar.c index 2caa871d2..905d67db7 100644 --- a/tests/clar.c +++ b/tests/clar.c @@ -340,6 +340,12 @@ clar_parse_args(int argc, char **argv) if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) { int exact = (arglen >= suitelen); + /* Do we have a real suite prefix separated by a + * trailing '::' or just a matching substring? */ + if (arglen > suitelen && (argument[suitelen] != ':' + || argument[suitelen + 1] != ':')) + continue; + ++found; if (!exact) @@ -409,7 +415,7 @@ clar_test_init(int argc, char **argv) } int -clar_test_run() +clar_test_run(void) { if (_clar.argc > 1) clar_parse_args(_clar.argc, _clar.argv); @@ -424,7 +430,7 @@ clar_test_run() } void -clar_test_shutdown() +clar_test_shutdown(void) { clar_print_shutdown( _clar.tests_ran, diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index d7e635302..fc08bbf1f 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -41,6 +41,51 @@ } \ } while(0) +/** + * Thread safe assertions; you cannot use `cl_git_report_failure` from a + * child thread since it will try to `longjmp` to abort and "the effect of + * a call to longjmp() where initialization of the jmp_buf structure was + * not performed in the calling thread is undefined." + * + * Instead, callers can provide a clar thread error context to a thread, + * which will populate and return it on failure. Callers can check the + * status with `cl_git_thread_check`. + */ +typedef struct { + int error; + const char *file; + int line; + const char *expr; + char error_msg[4096]; +} cl_git_thread_err; + +#ifdef GIT_THREADS +# define cl_git_thread_pass(threaderr, expr) cl_git_thread_pass_(threaderr, (expr), __FILE__, __LINE__) +#else +# define cl_git_thread_pass(threaderr, expr) cl_git_pass(expr) +#endif + +#define cl_git_thread_pass_(__threaderr, __expr, __file, __line) do { \ + giterr_clear(); \ + if ((((cl_git_thread_err *)__threaderr)->error = (__expr)) != 0) { \ + const git_error *_last = giterr_last(); \ + ((cl_git_thread_err *)__threaderr)->file = __file; \ + ((cl_git_thread_err *)__threaderr)->line = __line; \ + ((cl_git_thread_err *)__threaderr)->expr = "Function call failed: " #__expr; \ + p_snprintf(((cl_git_thread_err *)__threaderr)->error_msg, 4096, "thread 0x%" PRIxZ " - error %d - %s", \ + git_thread_currentid(), ((cl_git_thread_err *)__threaderr)->error, \ + _last ? _last->message : ""); \ + git_thread_exit(__threaderr); \ + } \ + } while (0) + +GIT_INLINE(void) cl_git_thread_check(void *data) +{ + cl_git_thread_err *threaderr = (cl_git_thread_err *)data; + if (threaderr->error != 0) + clar__assert(0, threaderr->file, threaderr->line, threaderr->expr, threaderr->error_msg, 1); +} + void cl_git_report_failure(int, const char *, int, const char *); #define cl_assert_at_line(expr,file,line) \ diff --git a/tests/commit/commit.c b/tests/commit/commit.c index c052cd568..b03169bc8 100644 --- a/tests/commit/commit.c +++ b/tests/commit/commit.c @@ -51,6 +51,66 @@ void test_commit_commit__create_unexisting_update_ref(void) git_reference_free(ref); } +void test_commit_commit__create_initial_commit(void) +{ + git_oid oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + git_reference *ref; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_fail(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + cl_git_pass(git_commit_create(&oid, _repo, "refs/heads/foo/bar", s, s, + NULL, "initial commit", tree, 0, NULL)); + + cl_git_pass(git_reference_lookup(&ref, _repo, "refs/heads/foo/bar")); + + cl_assert_equal_oid(&oid, git_reference_target(ref)); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); + git_reference_free(ref); +} + +void test_commit_commit__create_initial_commit_parent_not_current(void) +{ + git_oid oid; + git_oid original_oid; + git_tree *tree; + git_commit *commit; + git_signature *s; + + git_oid_fromstr(&oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750"); + cl_git_pass(git_commit_lookup(&commit, _repo, &oid)); + + git_oid_fromstr(&oid, "944c0f6e4dfa41595e6eb3ceecdb14f50fe18162"); + cl_git_pass(git_tree_lookup(&tree, _repo, &oid)); + + cl_git_pass(git_signature_now(&s, "alice", "alice@example.com")); + + cl_git_pass(git_reference_name_to_id(&original_oid, _repo, "HEAD")); + + cl_git_fail(git_commit_create(&oid, _repo, "HEAD", s, s, + NULL, "initial commit", tree, 0, NULL)); + + cl_git_pass(git_reference_name_to_id(&oid, _repo, "HEAD")); + + cl_assert_equal_oid(&oid, &original_oid); + + git_tree_free(tree); + git_commit_free(commit); + git_signature_free(s); +} + void assert_commit_summary(const char *expected, const char *given) { git_commit *dummy; diff --git a/tests/commit/signature.c b/tests/commit/signature.c index 0070320ae..30bc92967 100644 --- a/tests/commit/signature.c +++ b/tests/commit/signature.c @@ -86,3 +86,16 @@ void test_commit_signature__create_zero_char(void) cl_git_fail(git_signature_new(&sign, "", "x@y.z", 1234567890, 60)); cl_assert(sign == NULL); } + +void test_commit_signature__from_buf(void) +{ + git_signature *sign; + + cl_git_pass(git_signature_from_buffer(&sign, "Test User 1461698487 +0200")); + cl_assert_equal_s("Test User", sign->name); + cl_assert_equal_s("test@test.tt", sign->email); + cl_assert_equal_i(1461698487, sign->when.time); + cl_assert_equal_i(120, sign->when.offset); + git_signature_free(sign); +} + diff --git a/tests/commit/write.c b/tests/commit/write.c index 96b7cc321..030d41578 100644 --- a/tests/commit/write.c +++ b/tests/commit/write.c @@ -98,6 +98,45 @@ void test_commit_write__from_memory(void) cl_assert_equal_s(commit_message, git_commit_message(commit)); } +void test_commit_write__into_buf(void) +{ + git_oid tree_id; + git_signature *author, *committer; + git_tree *tree; + git_commit *parent; + git_oid parent_id; + git_buf commit = GIT_BUF_INIT; + + git_oid_fromstr(&tree_id, tree_id_str); + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + /* create signatures */ + cl_git_pass(git_signature_new(&committer, committer_name, committer_email, 123456789, 60)); + cl_git_pass(git_signature_new(&author, committer_name, committer_email, 987654321, 90)); + + git_oid_fromstr(&parent_id, parent_id_str); + cl_git_pass(git_commit_lookup(&parent, g_repo, &parent_id)); + + cl_git_pass(git_commit_create_buffer(&commit, g_repo, author, committer, + NULL, root_commit_message, tree, 1, (const git_commit **) &parent)); + + cl_assert_equal_s(commit.ptr, + "tree 1810dff58d8a660512d4832e740f692884338ccd\n\ +parent 8496071c1b46c854b31185ea97743be6a8774479\n\ +author Vicent Marti 987654321 +0130\n\ +committer Vicent Marti 123456789 +0100\n\ +\n\ +This is a root commit\n\ + This is a root commit and should be the only one in this branch\n\ +"); + + git_buf_free(&commit); + git_tree_free(tree); + git_commit_free(parent); + git_signature_free(author); + git_signature_free(committer); +} + // create a root commit void test_commit_write__root(void) { @@ -259,3 +298,103 @@ void test_commit_write__can_validate_objects(void) git_oid_fromstr(&parent_id, tree_id_str); cl_git_fail(create_commit_from_ids(&commit_id, &tree_id, &parent_id)); } + +void test_commit_write__attach_singleline_signature(void) +{ + const char *sig = "magic word: pretty please"; + + const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + + const char *complete = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +magicsig magic word: pretty please\n\ +\n\ +a simple commit which works\n"; + + git_oid id; + git_odb *odb; + git_odb_object *obj; + + cl_git_pass(git_commit_create_with_signature(&id, g_repo, data, sig, "magicsig")); + + cl_git_pass(git_repository_odb(&odb, g_repo)); + cl_git_pass(git_odb_read(&obj, odb, &id)); + cl_assert_equal_s(complete, git_odb_object_data(obj)); + + git_odb_object_free(obj); + git_odb_free(odb); +} + +void test_commit_write__attach_multiline_signature(void) +{ + const char *gpgsig = "-----BEGIN PGP SIGNATURE-----\n\ +Version: GnuPG v1.4.12 (Darwin)\n\ +\n\ +iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ +o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ +JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ +AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ +SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ +who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ +6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ +cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ +c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ +ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ +7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ +cpxtDQQMGYFpXK/71stq\n\ +=ozeK\n\ +-----END PGP SIGNATURE-----"; + + const char *data = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +\n\ +a simple commit which works\n"; + +const char *complete = "tree 6b79e22d69bf46e289df0345a14ca059dfc9bdf6\n\ +parent 34734e478d6cf50c27c9d69026d93974d052c454\n\ +author Ben Burkert 1358451456 -0800\n\ +committer Ben Burkert 1358451456 -0800\n\ +gpgsig -----BEGIN PGP SIGNATURE-----\n\ + Version: GnuPG v1.4.12 (Darwin)\n\ + \n\ + iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n\ + o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n\ + JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n\ + AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n\ + SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n\ + who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n\ + 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n\ + cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n\ + c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n\ + ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n\ + 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n\ + cpxtDQQMGYFpXK/71stq\n\ + =ozeK\n\ + -----END PGP SIGNATURE-----\n\ +\n\ +a simple commit which works\n"; + + git_oid one, two; + git_odb *odb; + git_odb_object *obj; + + cl_git_pass(git_commit_create_with_signature(&one, g_repo, data, gpgsig, "gpgsig")); + cl_git_pass(git_commit_create_with_signature(&two, g_repo, data, gpgsig, NULL)); + + cl_assert(!git_oid_cmp(&one, &two)); + cl_git_pass(git_repository_odb(&odb, g_repo)); + cl_git_pass(git_odb_read(&obj, odb, &one)); + cl_assert_equal_s(complete, git_odb_object_data(obj)); + + git_odb_object_free(obj); + git_odb_free(odb); +} diff --git a/tests/core/buffer.c b/tests/core/buffer.c index 9872af7f4..c4308cbbf 100644 --- a/tests/core/buffer.c +++ b/tests/core/buffer.c @@ -813,6 +813,44 @@ void test_core_buffer__encode_base85(void) git_buf_free(&buf); } +void test_core_buffer__decode_base85(void) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_decode_base85(&buf, "bZBXF", 5, 4)); + cl_assert_equal_sz(4, buf.size); + cl_assert_equal_s("this", buf.ptr); + git_buf_clear(&buf); + + cl_git_pass(git_buf_decode_base85(&buf, "ba!tca&BaE", 10, 8)); + cl_assert_equal_sz(8, buf.size); + cl_assert_equal_s("two rnds", buf.ptr); + git_buf_clear(&buf); + + cl_git_pass(git_buf_decode_base85(&buf, "bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", 30, 23)); + cl_assert_equal_sz(23, buf.size); + cl_assert_equal_s("this is base 85 encoded", buf.ptr); + git_buf_clear(&buf); + + git_buf_free(&buf); +} + +void test_core_buffer__decode_base85_fails_gracefully(void) +{ + git_buf buf = GIT_BUF_INIT; + + git_buf_puts(&buf, "foobar"); + + cl_git_fail(git_buf_decode_base85(&buf, "invalid charsZZ", 15, 42)); + cl_git_fail(git_buf_decode_base85(&buf, "invalidchars__ ", 15, 42)); + cl_git_fail(git_buf_decode_base85(&buf, "overflowZZ~~~~~", 15, 42)); + cl_git_fail(git_buf_decode_base85(&buf, "truncated", 9, 42)); + cl_assert_equal_sz(6, buf.size); + cl_assert_equal_s("foobar", buf.ptr); + + git_buf_free(&buf); +} + void test_core_buffer__classify_with_utf8(void) { char *data0 = "Simple text\n"; diff --git a/tests/core/encoding.c b/tests/core/encoding.c new file mode 100644 index 000000000..7d91720f4 --- /dev/null +++ b/tests/core/encoding.c @@ -0,0 +1,39 @@ +#include "clar_libgit2.h" +#include "varint.h" + +void test_core_encoding__decode(void) +{ + const unsigned char *buf = (unsigned char *)"AB"; + size_t size; + + cl_assert(git_decode_varint(buf, &size) == 65); + cl_assert(size == 1); + + buf = (unsigned char *)"\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 267869656); + cl_assert(size == 4); + + buf = (unsigned char *)"\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 1489279344088ULL); + cl_assert(size == 6); + + buf = (unsigned char *)"\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xfe\xdc\xbaXY"; + cl_assert(git_decode_varint(buf, &size) == 0); + cl_assert(size == 0); + +} + +void test_core_encoding__encode(void) +{ + unsigned char buf[100]; + cl_assert(git_encode_varint(buf, 100, 65) == 1); + cl_assert(buf[0] == 'A'); + + cl_assert(git_encode_varint(buf, 100, 267869656) == 4); + cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4)); + + cl_assert(git_encode_varint(buf, 100, 1489279344088ULL) == 6); + cl_assert(!memcmp(buf, "\xaa\xaa\xfe\xdc\xbaX", 6)); + + cl_assert(git_encode_varint(buf, 1, 1489279344088ULL) == -1); +} diff --git a/tests/core/env.c b/tests/core/env.c index ee08258a6..1af0e6e5d 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -298,3 +298,24 @@ void test_core_env__2(void) git_buf_free(&path); git_buf_free(&found); } + +void test_core_env__substitution(void) +{ + git_buf buf = GIT_BUF_INIT, expected = GIT_BUF_INIT; + + /* Set it to something non-default so we have controllable values */ + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, "/tmp/a")); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); + cl_assert_equal_s("/tmp/a", buf.ptr); + + git_buf_clear(&buf); + cl_git_pass(git_buf_join(&buf, GIT_PATH_LIST_SEPARATOR, "$PATH", "/tmp/b")); + cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, buf.ptr)); + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, &buf)); + + cl_git_pass(git_buf_join(&expected, GIT_PATH_LIST_SEPARATOR, "/tmp/a", "/tmp/b")); + cl_assert_equal_s(expected.ptr, buf.ptr); + + git_buf_free(&expected); + git_buf_free(&buf); +} diff --git a/tests/core/features.c b/tests/core/features.c index 85cddfeff..cf5e19063 100644 --- a/tests/core/features.c +++ b/tests/core/features.c @@ -17,11 +17,7 @@ void test_core_features__0(void) cl_assert((caps & GIT_FEATURE_THREADS) == 0); #endif -#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) cl_assert((caps & GIT_FEATURE_HTTPS) != 0); -#else - cl_assert((caps & GIT_FEATURE_HTTPS) == 0); -#endif #if defined(GIT_SSH) cl_assert((caps & GIT_FEATURE_SSH) != 0); diff --git a/tests/core/init.c b/tests/core/init.c index e17b7845f..a8cbd930b 100644 --- a/tests/core/init.c +++ b/tests/core/init.c @@ -12,3 +12,43 @@ void test_core_init__returns_count(void) cl_assert_equal_i(1, git_libgit2_shutdown()); } +void test_core_init__reinit_succeeds(void) +{ + cl_assert_equal_i(0, git_libgit2_shutdown()); + cl_assert_equal_i(1, git_libgit2_init()); + cl_sandbox_set_search_path_defaults(); +} + +#ifdef GIT_THREADS +static void *reinit(void *unused) +{ + unsigned i; + + for (i = 0; i < 20; i++) { + cl_assert(git_libgit2_init() > 0); + cl_assert(git_libgit2_shutdown() >= 0); + } + + return unused; +} +#endif + +void test_core_init__concurrent_init_succeeds(void) +{ +#ifdef GIT_THREADS + git_thread threads[10]; + unsigned i; + + cl_assert_equal_i(2, git_libgit2_init()); + + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_create(&threads[i], reinit, NULL); + for (i = 0; i < ARRAY_SIZE(threads); i++) + git_thread_join(&threads[i], NULL); + + cl_assert_equal_i(1, git_libgit2_shutdown()); + cl_sandbox_set_search_path_defaults(); +#else + cl_skip(); +#endif +} diff --git a/tests/core/path.c b/tests/core/path.c index c3e622f02..71c6eda58 100644 --- a/tests/core/path.c +++ b/tests/core/path.c @@ -652,3 +652,23 @@ void test_core_path__15_resolve_relative(void) git_buf_free(&buf); } + +#define assert_common_dirlen(i, p, q) \ + cl_assert_equal_i((i), git_path_common_dirlen((p), (q))); + +void test_core_path__16_resolve_relative(void) +{ + assert_common_dirlen(0, "", ""); + assert_common_dirlen(0, "", "bar.txt"); + assert_common_dirlen(0, "foo.txt", "bar.txt"); + assert_common_dirlen(0, "foo.txt", ""); + assert_common_dirlen(0, "foo/bar.txt", "bar/foo.txt"); + assert_common_dirlen(0, "foo/bar.txt", "../foo.txt"); + + assert_common_dirlen(1, "/one.txt", "/two.txt"); + assert_common_dirlen(4, "foo/one.txt", "foo/two.txt"); + assert_common_dirlen(5, "/foo/one.txt", "/foo/two.txt"); + + assert_common_dirlen(6, "a/b/c/foo.txt", "a/b/c/d/e/bar.txt"); + assert_common_dirlen(7, "/a/b/c/foo.txt", "/a/b/c/d/e/bar.txt"); +} diff --git a/tests/core/posix.c b/tests/core/posix.c index 34a67bf47..26ae36049 100644 --- a/tests/core/posix.c +++ b/tests/core/posix.c @@ -9,8 +9,11 @@ # endif #endif +#include + #include "clar_libgit2.h" #include "posix.h" +#include "userdiff.h" void test_core_posix__initialize(void) { @@ -39,7 +42,7 @@ void test_core_posix__inet_pton(void) struct in_addr addr; struct in6_addr addr6; size_t i; - + struct in_addr_data { const char *p; const uint8_t n[4]; @@ -146,3 +149,47 @@ void test_core_posix__utimes(void) p_unlink("foo"); } + +void test_core_posix__p_regcomp_ignores_global_locale_ctype(void) +{ + regex_t preg; + int error = 0; + + const char* oldlocale = setlocale(LC_CTYPE, NULL); + + if (!setlocale(LC_CTYPE, "UTF-8") && + !setlocale(LC_CTYPE, "c.utf8") && + !setlocale(LC_CTYPE, "en_US.UTF-8")) + cl_skip(); + + if (MB_CUR_MAX == 1) { + setlocale(LC_CTYPE, oldlocale); + cl_fail("Expected locale to be switched to multibyte"); + } + + p_regcomp(&preg, "[\xc0-\xff][\x80-\xbf]", REG_EXTENDED); + regfree(&preg); + + setlocale(LC_CTYPE, oldlocale); + + cl_must_pass(error); +} + +void test_core_posix__p_regcomp_compile_userdiff_regexps(void) +{ + size_t idx; + + for (idx = 0; idx < ARRAY_SIZE(builtin_defs); ++idx) { + git_diff_driver_definition ddef = builtin_defs[idx]; + int error = 0; + regex_t preg; + + error = p_regcomp(&preg, ddef.fns, REG_EXTENDED | ddef.flags); + regfree(&preg); + cl_must_pass(error); + + error = p_regcomp(&preg, ddef.words, REG_EXTENDED); + regfree(&preg); + cl_must_pass(error); + } +} diff --git a/tests/core/pqueue.c b/tests/core/pqueue.c index bcd4eea9f..2b90f4172 100644 --- a/tests/core/pqueue.c +++ b/tests/core/pqueue.c @@ -93,7 +93,29 @@ void test_core_pqueue__max_heap_size(void) cl_assert_equal_i(0, git_pqueue_size(&pq)); git_pqueue_free(&pq); +} +void test_core_pqueue__max_heap_size_without_comparison(void) +{ + git_pqueue pq; + int i, vals[100] = { 0 }; + + cl_git_pass(git_pqueue_init(&pq, GIT_PQUEUE_FIXED_SIZE, 50, NULL)); + + for (i = 0; i < 100; ++i) + cl_git_pass(git_pqueue_insert(&pq, &vals[i])); + + cl_assert_equal_i(50, git_pqueue_size(&pq)); + + /* As we have no comparison function, we cannot make any + * actual assumptions about which entries are part of the + * pqueue */ + for (i = 0; i < 50; ++i) + cl_assert(git_pqueue_pop(&pq)); + + cl_assert_equal_i(0, git_pqueue_size(&pq)); + + git_pqueue_free(&pq); } static int cmp_ints_like_commit_time(const void *a, const void *b) diff --git a/tests/core/useragent.c b/tests/core/useragent.c index 6d06693a8..5c09223bb 100644 --- a/tests/core/useragent.c +++ b/tests/core/useragent.c @@ -4,8 +4,14 @@ void test_core_useragent__get(void) { const char *custom_name = "super duper git"; + git_buf buf = GIT_BUF_INIT; cl_assert_equal_p(NULL, git_libgit2__user_agent()); cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name)); cl_assert_equal_s(custom_name, git_libgit2__user_agent()); + + cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf)); + cl_assert_equal_s(custom_name, buf.ptr); + + git_buf_free(&buf); } diff --git a/tests/core/vector.c b/tests/core/vector.c index 66f90b82b..c2e5d3f34 100644 --- a/tests/core/vector.c +++ b/tests/core/vector.c @@ -274,3 +274,136 @@ void test_core_vector__remove_matching(void) git_vector_free(&x); } + +static void assert_vector(git_vector *x, void *expected[], size_t len) +{ + size_t i; + + cl_assert_equal_i(len, x->length); + + for (i = 0; i < len; i++) + cl_assert(expected[i] == x->contents[i]); +} + +void test_core_vector__grow_and_shrink(void) +{ + git_vector x = GIT_VECTOR_INIT; + void *expected1[] = { + (void *)0x02, (void *)0x03, (void *)0x04, (void *)0x05, + (void *)0x06, (void *)0x07, (void *)0x08, (void *)0x09, + (void *)0x0a + }; + void *expected2[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x07, (void *)0x08, (void *)0x09, (void *)0x0a + }; + void *expected3[] = { + (void *)0x02, (void *)0x04, (void *)0x05, (void *)0x06, + (void *)0x0a + }; + void *expected4[] = { + (void *)0x02, (void *)0x04, (void *)0x05 + }; + void *expected5[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05 + }; + void *expected6[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x05, (void *)0x00 + }; + void *expected7[] = { + (void *)0x00, (void *)0x00, (void *)0x02, (void *)0x04, + (void *)0x00, (void *)0x00, (void *)0x00, (void *)0x05, + (void *)0x00 + }; + void *expected8[] = { + (void *)0x04, (void *)0x00, (void *)0x00, (void *)0x00, + (void *)0x05, (void *)0x00 + }; + void *expected9[] = { + (void *)0x04, (void *)0x00, (void *)0x05, (void *)0x00 + }; + void *expectedA[] = { (void *)0x04, (void *)0x00 }; + void *expectedB[] = { (void *)0x04 }; + + git_vector_insert(&x, (void *)0x01); + git_vector_insert(&x, (void *)0x02); + git_vector_insert(&x, (void *)0x03); + git_vector_insert(&x, (void *)0x04); + git_vector_insert(&x, (void *)0x05); + git_vector_insert(&x, (void *)0x06); + git_vector_insert(&x, (void *)0x07); + git_vector_insert(&x, (void *)0x08); + git_vector_insert(&x, (void *)0x09); + git_vector_insert(&x, (void *)0x0a); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, expected1, ARRAY_SIZE(expected1)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expected2, ARRAY_SIZE(expected2)); + + git_vector_remove_range(&x, 4, 3); + assert_vector(&x, expected3, ARRAY_SIZE(expected3)); + + git_vector_remove_range(&x, 3, 2); + assert_vector(&x, expected4, ARRAY_SIZE(expected4)); + + git_vector_insert_null(&x, 0, 2); + assert_vector(&x, expected5, ARRAY_SIZE(expected5)); + + git_vector_insert_null(&x, 5, 1); + assert_vector(&x, expected6, ARRAY_SIZE(expected6)); + + git_vector_insert_null(&x, 4, 3); + assert_vector(&x, expected7, ARRAY_SIZE(expected7)); + + git_vector_remove_range(&x, 0, 3); + assert_vector(&x, expected8, ARRAY_SIZE(expected8)); + + git_vector_remove_range(&x, 1, 2); + assert_vector(&x, expected9, ARRAY_SIZE(expected9)); + + git_vector_remove_range(&x, 2, 2); + assert_vector(&x, expectedA, ARRAY_SIZE(expectedA)); + + git_vector_remove_range(&x, 1, 1); + assert_vector(&x, expectedB, ARRAY_SIZE(expectedB)); + + git_vector_remove_range(&x, 0, 1); + assert_vector(&x, NULL, 0); + + git_vector_free(&x); +} + +void test_core_vector__reverse(void) +{ + git_vector v = GIT_VECTOR_INIT; + size_t i; + + void *in1[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3}; + void *out1[] = {(void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + void *in2[] = {(void *) 0x0, (void *) 0x1, (void *) 0x2, (void *) 0x3, (void *) 0x4}; + void *out2[] = {(void *) 0x4, (void *) 0x3, (void *) 0x2, (void *) 0x1, (void *) 0x0}; + + for (i = 0; i < 4; i++) + cl_git_pass(git_vector_insert(&v, in1[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 4; i++) + cl_assert_equal_p(out1[i], git_vector_get(&v, i)); + + git_vector_clear(&v); + for (i = 0; i < 5; i++) + cl_git_pass(git_vector_insert(&v, in2[i])); + + git_vector_reverse(&v); + + for (i = 0; i < 5; i++) + cl_assert_equal_p(out2[i], git_vector_get(&v, i)); + + git_vector_free(&v); +} diff --git a/tests/core/zstream.c b/tests/core/zstream.c index 7ba9424ba..961904ec3 100644 --- a/tests/core/zstream.c +++ b/tests/core/zstream.c @@ -48,7 +48,7 @@ void test_core_zstream__basic(void) char out[128]; size_t outlen = sizeof(out); - cl_git_pass(git_zstream_init(&z)); + cl_git_pass(git_zstream_init(&z, GIT_ZSTREAM_DEFLATE)); cl_git_pass(git_zstream_set_input(&z, data, strlen(data) + 1)); cl_git_pass(git_zstream_get_output(out, &outlen, &z)); cl_assert(git_zstream_done(&z)); @@ -58,6 +58,25 @@ void test_core_zstream__basic(void) assert_zlib_equal(data, strlen(data) + 1, out, outlen); } +void test_core_zstream__fails_on_trailing_garbage(void) +{ + git_buf deflated = GIT_BUF_INIT, inflated = GIT_BUF_INIT; + size_t i = 0; + + /* compress a simple string */ + git_zstream_deflatebuf(&deflated, "foobar!!", 8); + + /* append some garbage */ + for (i = 0; i < 10; i++) { + git_buf_putc(&deflated, i); + } + + cl_git_fail(git_zstream_inflatebuf(&inflated, deflated.ptr, deflated.size)); + + git_buf_free(&deflated); + git_buf_free(&inflated); +} + void test_core_zstream__buffer(void) { git_buf out = GIT_BUF_INIT; @@ -68,9 +87,10 @@ void test_core_zstream__buffer(void) #define BIG_STRING_PART "Big Data IS Big - Long Data IS Long - We need a buffer larger than 1024 x 1024 to make sure we trigger chunked compression - Big Big Data IS Bigger than Big - Long Long Data IS Longer than Long" -static void compress_input_various_ways(git_buf *input) +static void compress_and_decompress_input_various_ways(git_buf *input) { git_buf out1 = GIT_BUF_INIT, out2 = GIT_BUF_INIT; + git_buf inflated = GIT_BUF_INIT; size_t i, fixed_size = max(input->size / 2, 256); char *fixed = git__malloc(fixed_size); cl_assert(fixed); @@ -93,7 +113,7 @@ static void compress_input_various_ways(git_buf *input) } cl_assert(use_fixed_size <= fixed_size); - cl_git_pass(git_zstream_init(&zs)); + cl_git_pass(git_zstream_init(&zs, GIT_ZSTREAM_DEFLATE)); cl_git_pass(git_zstream_set_input(&zs, input->ptr, input->size)); while (!git_zstream_done(&zs)) { @@ -112,7 +132,12 @@ static void compress_input_various_ways(git_buf *input) git_buf_free(&out2); } + cl_git_pass(git_zstream_inflatebuf(&inflated, out1.ptr, out1.size)); + cl_assert_equal_i(input->size, inflated.size); + cl_assert(memcmp(input->ptr, inflated.ptr, inflated.size) == 0); + git_buf_free(&out1); + git_buf_free(&inflated); git__free(fixed); } @@ -129,14 +154,14 @@ void test_core_zstream__big_data(void) cl_git_pass( git_buf_put(&in, BIG_STRING_PART, strlen(BIG_STRING_PART))); - compress_input_various_ways(&in); + compress_and_decompress_input_various_ways(&in); /* make a big string that's hard to compress */ srand(0xabad1dea); for (scan = 0; scan < in.size; ++scan) in.ptr[scan] = (char)rand(); - compress_input_various_ways(&in); + compress_and_decompress_input_various_ways(&in); } git_buf_free(&in); diff --git a/tests/diff/diff_helpers.c b/tests/diff/diff_helpers.c index c6cdf803f..50752b203 100644 --- a/tests/diff/diff_helpers.c +++ b/tests/diff/diff_helpers.c @@ -241,3 +241,76 @@ void diff_print_raw(FILE *fp, git_diff *diff) git_diff_print(diff, GIT_DIFF_FORMAT_RAW, git_diff_print_callback__to_file_handle, fp ? fp : stderr)); } + +static size_t num_modified_deltas(git_diff *diff) +{ + const git_diff_delta *delta; + size_t i, cnt = 0; + + for (i = 0; i < git_diff_num_deltas(diff); i++) { + delta = git_diff_get_delta(diff, i); + + if (delta->status != GIT_DELTA_UNMODIFIED) + cnt++; + } + + return cnt; +} + +void diff_assert_equal(git_diff *a, git_diff *b) +{ + const git_diff_delta *ad, *bd; + size_t i, j; + + assert(a && b); + + cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b)); + + for (i = 0, j = 0; + i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) { + + ad = git_diff_get_delta(a, i); + bd = git_diff_get_delta(b, j); + + if (ad->status == GIT_DELTA_UNMODIFIED) { + i++; + continue; + } + if (bd->status == GIT_DELTA_UNMODIFIED) { + j++; + continue; + } + + cl_assert_equal_i(ad->status, bd->status); + cl_assert_equal_i(ad->flags, bd->flags); + cl_assert_equal_i(ad->similarity, bd->similarity); + cl_assert_equal_i(ad->nfiles, bd->nfiles); + + /* Don't examine the size or the flags of the deltas; + * computed deltas have sizes (parsed deltas do not) and + * computed deltas will have flags of `VALID_ID` and + * `EXISTS` (parsed deltas will not query the ODB.) + */ + + /* an empty id indicates that it wasn't presented, because + * the diff was identical. (eg, pure rename, mode change only, etc) + */ + if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) { + cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev); + cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id); + cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode); + } + cl_assert_equal_s(ad->old_file.path, bd->old_file.path); + + if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) { + cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id); + cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev); + cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode); + } + cl_assert_equal_s(ad->new_file.path, bd->new_file.path); + + i++; + j++; + } +} + diff --git a/tests/diff/diff_helpers.h b/tests/diff/diff_helpers.h index 4d3cd3474..520b654d3 100644 --- a/tests/diff/diff_helpers.h +++ b/tests/diff/diff_helpers.h @@ -68,3 +68,6 @@ extern int diff_foreach_via_iterator( extern void diff_print(FILE *fp, git_diff *diff); extern void diff_print_raw(FILE *fp, git_diff *diff); + +extern void diff_assert_equal(git_diff *a, git_diff *b); + diff --git a/tests/diff/format_email.c b/tests/diff/format_email.c index e55afe958..c3c04107b 100644 --- a/tests/diff/format_email.c +++ b/tests/diff/format_email.c @@ -4,6 +4,7 @@ #include "buffer.h" #include "commit.h" #include "diff.h" +#include "diff_generate.h" static git_repository *repo; @@ -112,7 +113,7 @@ void test_diff_format_email__with_message(void) "Also test if new paragraphs are included correctly.\n" \ "---\n" \ " file3.txt | 1 +\n" \ - " 1 file changed, 1 insertion(+), 0 deletions(-)\n" \ + " 1 file changed, 1 insertion(+)\n" \ "\n" \ "diff --git a/file3.txt b/file3.txt\n" \ "index 9a2d780..7309653 100644\n" \ @@ -155,7 +156,7 @@ void test_diff_format_email__multiple(void) "---\n" \ " file2.txt | 5 +++++\n" \ " file3.txt | 5 +++++\n" \ - " 2 files changed, 10 insertions(+), 0 deletions(-)\n" \ + " 2 files changed, 10 insertions(+)\n" \ " create mode 100644 file2.txt\n" \ " create mode 100644 file3.txt\n" \ "\n" \ @@ -350,9 +351,6 @@ void test_diff_format_email__mode_change(void) "diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \ "old mode 100644\n" \ "new mode 100755\n" \ - "index a97157a..a97157a\n" \ - "--- a/file1.txt.renamed\n" \ - "+++ b/file1.txt.renamed\n" \ "--\n" \ "libgit2 " LIBGIT2_VERSION "\n" \ "\n"; diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c deleted file mode 100644 index 25a23eda7..000000000 --- a/tests/diff/iterator.c +++ /dev/null @@ -1,1012 +0,0 @@ -#include "clar_libgit2.h" -#include "diff_helpers.h" -#include "iterator.h" -#include "tree.h" - -void test_diff_iterator__initialize(void) -{ - /* since we are doing tests with different sandboxes, defer setup - * to the actual tests. cleanup will still be done in the global - * cleanup function so that assertion failures don't result in a - * missed cleanup. - */ -} - -void test_diff_iterator__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - - -/* -- TREE ITERATOR TESTS -- */ - -static void tree_iterator_test( - const char *sandbox, - const char *treeish, - const char *start, - const char *end, - int expected_count, - const char **expected_values) -{ - git_tree *t; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, count = 0, count_post_reset = 0; - git_repository *repo = cl_git_sandbox_init(sandbox); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_assert(t = resolve_commit_oid_to_tree(repo, treeish)); - cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); - - /* test loop */ - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - if (expected_values != NULL) - cl_assert_equal_s(expected_values[count], entry->path); - count++; - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(expected_count, count); - - /* test reset */ - cl_git_pass(git_iterator_reset(i, NULL, NULL)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - if (expected_values != NULL) - cl_assert_equal_s(expected_values[count_post_reset], entry->path); - count_post_reset++; - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(count, count_post_reset); - - git_iterator_free(i); - git_tree_free(t); -} - -/* results of: git ls-tree -r --name-only 605812a */ -const char *expected_tree_0[] = { - ".gitattributes", - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_diff_iterator__tree_0(void) -{ - tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0); -} - -/* results of: git ls-tree -r --name-only 6bab5c79 */ -const char *expected_tree_1[] = { - ".gitattributes", - "attr0", - "attr1", - "attr2", - "attr3", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "subdir/.gitattributes", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_diff_iterator__tree_1(void) -{ - tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1); -} - -/* results of: git ls-tree -r --name-only 26a125ee1 */ -const char *expected_tree_2[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL -}; - -void test_diff_iterator__tree_2(void) -{ - tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2); -} - -/* $ git ls-tree -r --name-only 0017bd4ab1e */ -const char *expected_tree_3[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file" -}; - -void test_diff_iterator__tree_3(void) -{ - tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3); -} - -/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */ -const char *expected_tree_4[] = { - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "sub/abc", - "sub/file", - "sub/sub/file", - "sub/sub/subsub.txt", - "sub/subdir_test1", - "sub/subdir_test2.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", - NULL -}; - -void test_diff_iterator__tree_4(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL, - 23, expected_tree_4); -} - -void test_diff_iterator__tree_4_ranged(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "sub", "sub", - 11, &expected_tree_4[12]); -} - -const char *expected_tree_ranged_0[] = { - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - NULL -}; - -void test_diff_iterator__tree_ranged_0(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "git", "root", - 7, expected_tree_ranged_0); -} - -const char *expected_tree_ranged_1[] = { - "sub/subdir_test2.txt", - NULL -}; - -void test_diff_iterator__tree_ranged_1(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "sub/subdir_test2.txt", "sub/subdir_test2.txt", - 1, expected_tree_ranged_1); -} - -void test_diff_iterator__tree_range_empty_0(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "empty", "empty", 0, NULL); -} - -void test_diff_iterator__tree_range_empty_1(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - "z_empty_after", NULL, 0, NULL); -} - -void test_diff_iterator__tree_range_empty_2(void) -{ - tree_iterator_test( - "attr", "24fa9a9fc4e202313e24b648087495441dab432b", - NULL, ".aaa_empty_before", 0, NULL); -} - -static void check_tree_entry( - git_iterator *i, - const char *oid, - const char *oid_p, - const char *oid_pp, - const char *oid_ppp) -{ - const git_index_entry *ie; - const git_tree_entry *te; - const git_tree *tree; - git_buf path = GIT_BUF_INIT; - - cl_git_pass(git_iterator_current_tree_entry(&te, i)); - cl_assert(te); - cl_assert(git_oid_streq(te->oid, oid) == 0); - - cl_git_pass(git_iterator_current(&ie, i)); - cl_git_pass(git_buf_sets(&path, ie->path)); - - if (oid_p) { - git_buf_rtruncate_at_char(&path, '/'); - cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0); - } - - if (oid_pp) { - git_buf_rtruncate_at_char(&path, '/'); - cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0); - } - - if (oid_ppp) { - git_buf_rtruncate_at_char(&path, '/'); - cl_git_pass(git_iterator_current_parent_tree(&tree, i, path.ptr)); - cl_assert(tree); - cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0); - } - - git_buf_free(&path); -} - -void test_diff_iterator__tree_special_functions(void) -{ - git_tree *t; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - git_repository *repo = cl_git_sandbox_init("attr"); - int error, cases = 0; - const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e"; - - t = resolve_commit_oid_to_tree( - repo, "24fa9a9fc4e202313e24b648087495441dab432b"); - cl_assert(t != NULL); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - - if (strcmp(entry->path, "sub/file") == 0) { - cases++; - check_tree_entry( - i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "ecb97df2a174987475ac816e3847fc8e9f6c596b", - rootoid, NULL); - } - else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) { - cases++; - check_tree_entry( - i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242", - "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485", - "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid); - } - else if (strcmp(entry->path, "subdir/.gitattributes") == 0) { - cases++; - check_tree_entry( - i, "99eae476896f4907224978b88e5ecaa6c5bb67a9", - "9fb40b6675dde60b5697afceae91b66d908c02d9", - rootoid, NULL); - } - else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) { - cases++; - check_tree_entry( - i, "dccada462d3df8ac6de596fb8c896aba9344f941", - "2929de282ce999e95183aedac6451d3384559c4b", - rootoid, NULL); - } - } - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(4, cases); - - git_iterator_free(i); - git_tree_free(t); -} - -/* -- INDEX ITERATOR TESTS -- */ - -static void index_iterator_test( - const char *sandbox, - const char *start, - const char *end, - git_iterator_flag_t flags, - int expected_count, - const char **expected_names, - const char **expected_oids) -{ - git_index *index; - git_iterator *i; - const git_index_entry *entry; - int error, count = 0, caps; - git_repository *repo = cl_git_sandbox_init(sandbox); - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - cl_git_pass(git_repository_index(&index, repo)); - caps = git_index_caps(index); - - iter_opts.flags = flags; - iter_opts.start = start; - iter_opts.end = end; - - cl_git_pass(git_iterator_for_index(&i, repo, index, &iter_opts)); - - while (!(error = git_iterator_advance(&entry, i))) { - cl_assert(entry); - - if (expected_names != NULL) - cl_assert_equal_s(expected_names[count], entry->path); - - if (expected_oids != NULL) { - git_oid oid; - cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); - cl_assert_equal_oid(&oid, &entry->id); - } - - count++; - } - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert(!entry); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - - cl_assert(caps == git_index_caps(index)); - git_index_free(index); -} - -static const char *expected_index_0[] = { - "attr0", - "attr1", - "attr2", - "attr3", - "binfile", - "gitattributes", - "macro_bad", - "macro_test", - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", - "sub/abc", - "sub/file", - "sub/sub/file", - "sub/sub/subsub.txt", - "sub/subdir_test1", - "sub/subdir_test2.txt", - "subdir/.gitattributes", - "subdir/abc", - "subdir/subdir_test1", - "subdir/subdir_test2.txt", - "subdir2/subdir2_test1", -}; - -static const char *expected_index_oids_0[] = { - "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", - "3b74db7ab381105dc0d28f8295a77f6a82989292", - "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", - "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", - "d800886d9c86731ae5c4a62b0b77c437015e00d2", - "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", - "5819a185d77b03325aaf87cafc771db36f6ddca7", - "ff69f8639ce2e6010b3f33a74160aad98b48da2b", - "45141a79a77842c59a63229403220a4e4be74e3d", - "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", - "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", - "3e42ffc54a663f9401cc25843d6c0e71a33e4249", - "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", - "9e5bdc47d6a80f2be0ea3049ad74231b94609242", - "e563cf4758f0d646f1b14b76016aa17fa9e549a4", - "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", - "99eae476896f4907224978b88e5ecaa6c5bb67a9", - "3e42ffc54a663f9401cc25843d6c0e71a33e4249", - "e563cf4758f0d646f1b14b76016aa17fa9e549a4", - "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", - "dccada462d3df8ac6de596fb8c896aba9344f941" -}; - -void test_diff_iterator__index_0(void) -{ - index_iterator_test( - "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0), - expected_index_0, expected_index_oids_0); -} - -static const char *expected_index_range[] = { - "root_test1", - "root_test2", - "root_test3", - "root_test4.txt", -}; - -static const char *expected_index_oids_range[] = { - "45141a79a77842c59a63229403220a4e4be74e3d", - "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", - "108bb4e7fd7b16490dc33ff7d972151e73d7166e", - "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", -}; - -void test_diff_iterator__index_range(void) -{ - index_iterator_test( - "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range), - expected_index_range, expected_index_oids_range); -} - -void test_diff_iterator__index_range_empty_0(void) -{ - index_iterator_test( - "attr", "empty", "empty", 0, 0, NULL, NULL); -} - -void test_diff_iterator__index_range_empty_1(void) -{ - index_iterator_test( - "attr", "z_empty_after", NULL, 0, 0, NULL, NULL); -} - -void test_diff_iterator__index_range_empty_2(void) -{ - index_iterator_test( - "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL); -} - -static const char *expected_index_1[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_new_file", - "staged_new_file_deleted_file", - "staged_new_file_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", -}; - -static const char* expected_index_oids_1[] = { - "a0de7e0ac200c489c41c59dfa910154a70264e6e", - "5452d32f1dd538eb0405e8a83cc185f79e25e80f", - "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", - "55d316c9ba708999f1918e9677d01dfcae69c6b9", - "a6be623522ce87a1d862128ac42672604f7b468b", - "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", - "529a16e8e762d4acb7b9636ff540a00831f9155a", - "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", - "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", - "e8ee89e15bbe9b20137715232387b3de5b28972e", - "53ace0d1cc1145a5f4fe4f78a186a60263190733", - "1888c805345ba265b0ee9449b8877b6064592058", - "a6191982709b746d5650e93c2acf34ef74e11504" -}; - -void test_diff_iterator__index_1(void) -{ - index_iterator_test( - "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1), - expected_index_1, expected_index_oids_1); -} - -static const char *expected_index_cs[] = { - "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c", - "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c", -}; - -static const char *expected_index_ci[] = { - "a", "B", "c", "D", "e", "F", "g", "H", "i", "J", - "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D", -}; - -void test_diff_iterator__index_case_folding(void) -{ - git_buf path = GIT_BUF_INIT; - int fs_is_ci = 0; - - cl_git_pass(git_buf_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg")); - fs_is_ci = git_path_exists(path.ptr); - git_buf_free(&path); - - index_iterator_test( - "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs), - fs_is_ci ? expected_index_ci : expected_index_cs, NULL); - - cl_git_sandbox_cleanup(); - - index_iterator_test( - "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE, - ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL); - - cl_git_sandbox_cleanup(); - - index_iterator_test( - "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE, - ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL); -} - -/* -- WORKDIR ITERATOR TESTS -- */ - -static void workdir_iterator_test( - const char *sandbox, - const char *start, - const char *end, - int expected_count, - int expected_ignores, - const char **expected_names, - const char *an_ignored_name) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, count = 0, count_all = 0, count_all_post_reset = 0; - git_repository *repo = cl_git_sandbox_init(sandbox); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_workdir(&i, repo, NULL, NULL, &i_opts)); - - error = git_iterator_current(&entry, i); - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - - while (entry != NULL) { - int ignored = git_iterator_current_is_ignored(i); - - if (S_ISDIR(entry->mode)) { - cl_git_pass(git_iterator_advance_into(&entry, i)); - continue; - } - - if (expected_names != NULL) - cl_assert_equal_s(expected_names[count_all], entry->path); - - if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) - cl_assert(ignored); - - if (!ignored) - count++; - count_all++; - - error = git_iterator_advance(&entry, i); - - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - } - - cl_assert_equal_i(expected_count, count); - cl_assert_equal_i(expected_count + expected_ignores, count_all); - - cl_git_pass(git_iterator_reset(i, NULL, NULL)); - - error = git_iterator_current(&entry, i); - cl_assert((error == 0 && entry != NULL) || - (error == GIT_ITEROVER && entry == NULL)); - - while (entry != NULL) { - if (S_ISDIR(entry->mode)) { - cl_git_pass(git_iterator_advance_into(&entry, i)); - continue; - } - - if (expected_names != NULL) - cl_assert_equal_s( - expected_names[count_all_post_reset], entry->path); - count_all_post_reset++; - - error = git_iterator_advance(&entry, i); - cl_assert(error == 0 || error == GIT_ITEROVER); - } - - cl_assert_equal_i(count_all, count_all_post_reset); - - git_iterator_free(i); -} - -void test_diff_iterator__workdir_0(void) -{ - workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign"); -} - -static const char *status_paths[] = { - "current_file", - "ignored_file", - "modified_file", - "new_file", - "staged_changes", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", - NULL -}; - -void test_diff_iterator__workdir_1(void) -{ - workdir_iterator_test( - "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); -} - -static const char *status_paths_range_0[] = { - "staged_changes", - "staged_changes_modified_file", - "staged_delete_modified_file", - "staged_new_file", - "staged_new_file_modified_file", - NULL -}; - -void test_diff_iterator__workdir_1_ranged_0(void) -{ - workdir_iterator_test( - "status", "staged", "staged", 5, 0, status_paths_range_0, NULL); -} - -static const char *status_paths_range_1[] = { - "modified_file", NULL -}; - -void test_diff_iterator__workdir_1_ranged_1(void) -{ - workdir_iterator_test( - "status", "modified_file", "modified_file", - 1, 0, status_paths_range_1, NULL); -} - -static const char *status_paths_range_3[] = { - "subdir.txt", - "subdir/current_file", - "subdir/modified_file", - NULL -}; - -void test_diff_iterator__workdir_1_ranged_3(void) -{ - workdir_iterator_test( - "status", "subdir", "subdir/modified_file", - 3, 0, status_paths_range_3, NULL); -} - -static const char *status_paths_range_4[] = { - "subdir/current_file", - "subdir/modified_file", - "subdir/new_file", - "\xe8\xbf\x99", - NULL -}; - -void test_diff_iterator__workdir_1_ranged_4(void) -{ - workdir_iterator_test( - "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); -} - -static const char *status_paths_range_5[] = { - "subdir/modified_file", - NULL -}; - -void test_diff_iterator__workdir_1_ranged_5(void) -{ - workdir_iterator_test( - "status", "subdir/modified_file", "subdir/modified_file", - 1, 0, status_paths_range_5, NULL); -} - -void test_diff_iterator__workdir_1_ranged_empty_0(void) -{ - workdir_iterator_test( - "status", "\xff_does_not_exist", NULL, - 0, 0, NULL, NULL); -} - -void test_diff_iterator__workdir_1_ranged_empty_1(void) -{ - workdir_iterator_test( - "status", "empty", "empty", - 0, 0, NULL, NULL); -} - -void test_diff_iterator__workdir_1_ranged_empty_2(void) -{ - workdir_iterator_test( - "status", NULL, "aaaa_empty_before", - 0, 0, NULL, NULL); -} - -void test_diff_iterator__workdir_builtin_ignores(void) -{ - git_repository *repo = cl_git_sandbox_init("attr"); - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int idx; - static struct { - const char *path; - bool ignored; - } expected[] = { - { "dir/", true }, - { "file", false }, - { "ign", true }, - { "macro_bad", false }, - { "macro_test", false }, - { "root_test1", false }, - { "root_test2", false }, - { "root_test3", false }, - { "root_test4.txt", false }, - { "sub/", false }, - { "sub/.gitattributes", false }, - { "sub/abc", false }, - { "sub/dir/", true }, - { "sub/file", false }, - { "sub/ign/", true }, - { "sub/sub/", false }, - { "sub/sub/.gitattributes", false }, - { "sub/sub/dir", false }, /* file is not actually a dir */ - { "sub/sub/file", false }, - { NULL, false } - }; - - cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); - cl_git_mkfile("attr/sub/.git", "whatever"); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = "dir"; - i_opts.end = "sub/sub/file"; - - cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, &i_opts)); - cl_git_pass(git_iterator_current(&entry, i)); - - for (idx = 0; entry != NULL; ++idx) { - int ignored = git_iterator_current_is_ignored(i); - - cl_assert_equal_s(expected[idx].path, entry->path); - cl_assert_(ignored == expected[idx].ignored, expected[idx].path); - - if (!ignored && - (entry->mode == GIT_FILEMODE_TREE || - entry->mode == GIT_FILEMODE_COMMIT)) - { - /* it is possible to advance "into" a submodule */ - cl_git_pass(git_iterator_advance_into(&entry, i)); - } else { - int error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - } - - cl_assert(expected[idx].path == NULL); - - git_iterator_free(i); -} - -static void check_wd_first_through_third_range( - git_repository *repo, const char *start, const char *end) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - const git_index_entry *entry; - int error, idx; - static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, &i_opts)); - cl_git_pass(git_iterator_current(&entry, i)); - - for (idx = 0; entry != NULL; ++idx) { - cl_assert_equal_s(expected[idx], entry->path); - - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - - cl_assert(expected[idx] == NULL); - - git_iterator_free(i); -} - -void test_diff_iterator__workdir_handles_icase_range(void) -{ - git_repository *repo; - - repo = cl_git_sandbox_init("empty_standard_repo"); - cl_git_remove_placeholders(git_repository_path(repo), "dummy-marker.txt"); - - cl_git_mkfile("empty_standard_repo/before", "whatever\n"); - cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n"); - cl_git_mkfile("empty_standard_repo/second", "whatever\n"); - cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n"); - cl_git_mkfile("empty_standard_repo/zafter", "whatever\n"); - cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n"); - - check_wd_first_through_third_range(repo, "first", "third"); - check_wd_first_through_third_range(repo, "FIRST", "THIRD"); - check_wd_first_through_third_range(repo, "first", "THIRD"); - check_wd_first_through_third_range(repo, "FIRST", "third"); - check_wd_first_through_third_range(repo, "FirSt", "tHiRd"); -} - -static void check_tree_range( - git_repository *repo, - const char *start, - const char *end, - bool ignore_case, - int expected_count) -{ - git_tree *head; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, count; - - i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_repository_head_tree(&head, repo)); - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - - for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) - /* count em up */; - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - git_tree_free(head); -} - -void test_diff_iterator__tree_handles_icase_range(void) -{ - git_repository *repo; - - repo = cl_git_sandbox_init("testrepo"); - - check_tree_range(repo, "B", "C", false, 0); - check_tree_range(repo, "B", "C", true, 1); - check_tree_range(repo, "b", "c", false, 1); - check_tree_range(repo, "b", "c", true, 1); - - check_tree_range(repo, "a", "z", false, 3); - check_tree_range(repo, "a", "z", true, 4); - check_tree_range(repo, "A", "Z", false, 1); - check_tree_range(repo, "A", "Z", true, 4); - check_tree_range(repo, "a", "Z", false, 0); - check_tree_range(repo, "a", "Z", true, 4); - check_tree_range(repo, "A", "z", false, 4); - check_tree_range(repo, "A", "z", true, 4); - - check_tree_range(repo, "new.txt", "new.txt", true, 1); - check_tree_range(repo, "new.txt", "new.txt", false, 1); - check_tree_range(repo, "README", "README", true, 1); - check_tree_range(repo, "README", "README", false, 1); -} - -static void check_index_range( - git_repository *repo, - const char *start, - const char *end, - bool ignore_case, - int expected_count) -{ - git_index *index; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - int error, count, caps; - bool is_ignoring_case; - - cl_git_pass(git_repository_index(&index, repo)); - - caps = git_index_caps(index); - is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0); - - if (ignore_case != is_ignoring_case) - cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE)); - - i_opts.flags = 0; - i_opts.start = start; - i_opts.end = end; - - cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts)); - - cl_assert(git_iterator_ignore_case(i) == ignore_case); - - for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) - /* count em up */; - - cl_assert_equal_i(GIT_ITEROVER, error); - cl_assert_equal_i(expected_count, count); - - git_iterator_free(i); - git_index_free(index); -} - -void test_diff_iterator__index_handles_icase_range(void) -{ - git_repository *repo; - git_index *index; - git_tree *head; - - repo = cl_git_sandbox_init("testrepo"); - - /* reset index to match HEAD */ - cl_git_pass(git_repository_head_tree(&head, repo)); - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_read_tree(index, head)); - cl_git_pass(git_index_write(index)); - git_tree_free(head); - git_index_free(index); - - /* do some ranged iterator checks toggling case sensitivity */ - check_index_range(repo, "B", "C", false, 0); - check_index_range(repo, "B", "C", true, 1); - check_index_range(repo, "a", "z", false, 3); - check_index_range(repo, "a", "z", true, 4); -} diff --git a/tests/diff/parse.c b/tests/diff/parse.c new file mode 100644 index 000000000..a06813d1b --- /dev/null +++ b/tests/diff/parse.c @@ -0,0 +1,198 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" +#include "diff_helpers.h" +#include "../src/diff.h" + +#include "../patch/patch_common.h" + +void test_diff_parse__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_diff_parse__nonpatches_fail_with_notfound(void) +{ + git_diff *diff; + const char *not = PATCH_NOT_A_PATCH; + const char *not_with_leading = "Leading text.\n" PATCH_NOT_A_PATCH; + const char *not_with_trailing = PATCH_NOT_A_PATCH "Trailing text.\n"; + const char *not_with_both = "Lead.\n" PATCH_NOT_A_PATCH "Trail.\n"; + + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not, + strlen(not))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_leading, + strlen(not_with_leading))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_trailing, + strlen(not_with_trailing))); + cl_git_fail_with(GIT_ENOTFOUND, + git_diff_from_buffer(&diff, + not_with_both, + strlen(not_with_both))); +} + +static void test_parse_invalid_diff(const char *invalid_diff) +{ + git_diff *diff; + git_buf buf = GIT_BUF_INIT; + + /* throw some random (legitimate) diffs in with the given invalid + * one. + */ + git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE); + git_buf_puts(&buf, PATCH_BINARY_DELTA); + git_buf_puts(&buf, invalid_diff); + git_buf_puts(&buf, PATCH_ORIGINAL_TO_CHANGE_MIDDLE); + git_buf_puts(&buf, PATCH_BINARY_LITERAL); + + cl_git_fail_with(GIT_ERROR, + git_diff_from_buffer(&diff, buf.ptr, buf.size)); + + git_buf_free(&buf); +} + +void test_diff_parse__invalid_patches_fails(void) +{ + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_NEW_FILE); + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_OLD_FILE); + test_parse_invalid_diff(PATCH_CORRUPT_NO_CHANGES); + test_parse_invalid_diff(PATCH_CORRUPT_MISSING_HUNK_HEADER); +} + +static void test_tree_to_tree_computed_to_parsed( + const char *sandbox, const char *a_id, const char *b_id, + uint32_t diff_flags, uint32_t find_flags) +{ + git_repository *repo; + git_diff *computed, *parsed; + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf computed_buf = GIT_BUF_INIT; + + repo = cl_git_sandbox_init(sandbox); + + opts.id_abbrev = GIT_OID_HEXSZ; + opts.flags = GIT_DIFF_SHOW_BINARY | diff_flags; + findopts.flags = find_flags; + + cl_assert((a = resolve_commit_oid_to_tree(repo, a_id)) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(repo, b_id)) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); + + if (find_flags) + cl_git_pass(git_diff_find_similar(computed, &findopts)); + + cl_git_pass(git_diff_to_buf(&computed_buf, + computed, GIT_DIFF_FORMAT_PATCH)); + + cl_git_pass(git_diff_from_buffer(&parsed, + computed_buf.ptr, computed_buf.size)); + + diff_assert_equal(computed, parsed); + + git_tree_free(a); + git_tree_free(b); + + git_diff_free(computed); + git_diff_free(parsed); + + git_buf_free(&computed_buf); + + cl_git_sandbox_cleanup(); +} + +void test_diff_parse__can_parse_generated_diff(void) +{ + test_tree_to_tree_computed_to_parsed( + "diff", "d70d245e", "7a9e0b02", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "806999", "a8595c", 0, 0); + test_tree_to_tree_computed_to_parsed("diff", + "d70d245ed97ed2aa596dd1af6536e4bfdb047b69", + "7a9e0b02e63179929fed24f0a3e0f19168114d10", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "7fccd7", "806999", 0, 0); + test_tree_to_tree_computed_to_parsed( + "unsymlinked.git", "7fccd7", "a8595c", 0, 0); + test_tree_to_tree_computed_to_parsed( + "attr", "605812a", "370fe9ec22", 0, 0); + test_tree_to_tree_computed_to_parsed( + "attr", "f5b0af1fb4f5c", "370fe9ec22", 0, 0); + test_tree_to_tree_computed_to_parsed( + "diff", "d70d245e", "d70d245e", 0, 0); + test_tree_to_tree_computed_to_parsed("diff_format_email", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + GIT_DIFF_SHOW_BINARY, 0); + test_tree_to_tree_computed_to_parsed("diff_format_email", + "897d3af16ca9e420cd071b1c4541bd2b91d04c8c", + "873806f6f27e631eb0b23e4b56bea2bfac14a373", + GIT_DIFF_SHOW_BINARY, 0); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + 0, GIT_DIFF_FIND_RENAMES); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + GIT_DIFF_INCLUDE_UNMODIFIED, + 0); + test_tree_to_tree_computed_to_parsed("renames", + "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2", + "2bc7f351d20b53f1c72c16c4b036e491c478c49a", + GIT_DIFF_INCLUDE_UNMODIFIED, + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | GIT_DIFF_FIND_EXACT_MATCH_ONLY); +} + +void test_diff_parse__get_patch_from_diff(void) +{ + git_repository *repo; + git_diff *computed, *parsed; + git_tree *a, *b; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_buf computed_buf = GIT_BUF_INIT; + git_patch *patch_computed, *patch_parsed; + + repo = cl_git_sandbox_init("diff"); + + opts.flags = GIT_DIFF_SHOW_BINARY; + + cl_assert((a = resolve_commit_oid_to_tree(repo, + "d70d245ed97ed2aa596dd1af6536e4bfdb047b69")) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(repo, + "7a9e0b02e63179929fed24f0a3e0f19168114d10")) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&computed, repo, a, b, &opts)); + cl_git_pass(git_diff_to_buf(&computed_buf, + computed, GIT_DIFF_FORMAT_PATCH)); + cl_git_pass(git_patch_from_diff(&patch_computed, computed, 0)); + + cl_git_pass(git_diff_from_buffer(&parsed, + computed_buf.ptr, computed_buf.size)); + cl_git_pass(git_patch_from_diff(&patch_parsed, parsed, 0)); + + cl_assert_equal_i( + git_patch_num_hunks(patch_computed), + git_patch_num_hunks(patch_parsed)); + + git_patch_free(patch_computed); + git_patch_free(patch_parsed); + + git_tree_free(a); + git_tree_free(b); + + git_diff_free(computed); + git_diff_free(parsed); + + git_buf_free(&computed_buf); + + cl_git_sandbox_cleanup(); +} diff --git a/tests/diff/racediffiter.c b/tests/diff/racediffiter.c new file mode 100644 index 000000000..d364d6b21 --- /dev/null +++ b/tests/diff/racediffiter.c @@ -0,0 +1,129 @@ +/* This test exercises the problem described in +** https://github.com/libgit2/libgit2/pull/3568 +** where deleting a directory during a diff/status +** operation can cause an access violation. +** +** The "test_diff_racediffiter__basic() test confirms +** the normal operation of diff on the given repo. +** +** The "test_diff_racediffiter__racy_rmdir() test +** uses the new diff progress callback to delete +** a directory (after the initial readdir() and +** before the directory itself is visited) causing +** the recursion and iteration to fail. +*/ + +#include "clar_libgit2.h" +#include "diff_helpers.h" + +#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0])) + +void test_diff_racediffiter__initialize(void) +{ +} + +void test_diff_racediffiter__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +typedef struct +{ + const char *path; + git_delta_t t; + +} basic_payload; + +static int notify_cb__basic( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + basic_payload *exp = (basic_payload *)payload; + basic_payload *e; + + GIT_UNUSED(diff_so_far); + GIT_UNUSED(matched_pathspec); + + for (e = exp; e->path; e++) { + if (strcmp(e->path, delta_to_add->new_file.path) == 0) { + cl_assert_equal_i(e->t, delta_to_add->status); + return 0; + } + } + cl_assert(0); + return GIT_ENOTFOUND; +} + +void test_diff_racediffiter__basic(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_repository *repo = cl_git_sandbox_init("diff"); + git_diff *diff; + + basic_payload exp_a[] = { + { "another.txt", GIT_DELTA_MODIFIED }, + { "readme.txt", GIT_DELTA_MODIFIED }, + { "zzzzz/", GIT_DELTA_IGNORED }, + { NULL, 0 } + }; + + cl_must_pass(p_mkdir("diff/zzzzz", 0777)); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + opts.notify_cb = notify_cb__basic; + opts.payload = exp_a; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + git_diff_free(diff); +} + + +typedef struct { + bool first_time; + const char *dir; + basic_payload *basic_payload; +} racy_payload; + +static int notify_cb__racy_rmdir( + const git_diff *diff_so_far, + const git_diff_delta *delta_to_add, + const char *matched_pathspec, + void *payload) +{ + racy_payload *pay = (racy_payload *)payload; + + if (pay->first_time) { + cl_must_pass(p_rmdir(pay->dir)); + pay->first_time = false; + } + + return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload); +} + +void test_diff_racediffiter__racy(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_repository *repo = cl_git_sandbox_init("diff"); + git_diff *diff; + + basic_payload exp_a[] = { + { "another.txt", GIT_DELTA_MODIFIED }, + { "readme.txt", GIT_DELTA_MODIFIED }, + { NULL, 0 } + }; + + racy_payload pay = { true, "diff/zzzzz", exp_a }; + + cl_must_pass(p_mkdir("diff/zzzzz", 0777)); + + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS; + opts.notify_cb = notify_cb__racy_rmdir; + opts.payload = &pay; + + cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts)); + + git_diff_free(diff); +} diff --git a/tests/diff/rename.c b/tests/diff/rename.c index 5cfd8e235..c1cd25239 100644 --- a/tests/diff/rename.c +++ b/tests/diff/rename.c @@ -1702,3 +1702,49 @@ void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE); } + +/* test that 100% renames and copies emit the correct patch file + * git diff --find-copies-harder -M100 -B100 \ + * 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \ + * 2bc7f351d20b53f1c72c16c4b036e491c478c49a + */ +void test_diff_rename__identical(void) +{ + const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2"; + const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a"; + git_tree *old_tree, *new_tree; + git_diff *diff; + git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; + git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT; + git_buf diff_buf = GIT_BUF_INIT; + const char *expected = + "diff --git a/serving.txt b/sixserving.txt\n" + "similarity index 100%\n" + "rename from serving.txt\n" + "rename to sixserving.txt\n" + "diff --git a/sevencities.txt b/songofseven.txt\n" + "similarity index 100%\n" + "copy from sevencities.txt\n" + "copy to songofseven.txt\n"; + + old_tree = resolve_commit_oid_to_tree(g_repo, old_sha); + new_tree = resolve_commit_oid_to_tree(g_repo, new_sha); + + diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED; + find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED | + GIT_DIFF_FIND_EXACT_MATCH_ONLY; + + cl_git_pass(git_diff_tree_to_tree(&diff, + g_repo, old_tree, new_tree, &diff_opts)); + cl_git_pass(git_diff_find_similar(diff, &find_opts)); + + cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH)); + + cl_assert_equal_s(expected, diff_buf.ptr); + + git_buf_free(&diff_buf); + git_diff_free(diff); + git_tree_free(old_tree); + git_tree_free(new_tree); +} + diff --git a/tests/diff/stats.c b/tests/diff/stats.c index f731997da..3171a4486 100644 --- a/tests/diff/stats.c +++ b/tests/diff/stats.c @@ -4,6 +4,7 @@ #include "buffer.h" #include "commit.h" #include "diff.h" +#include "diff_generate.h" static git_repository *_repo; static git_diff_stats *_stats; @@ -113,6 +114,42 @@ void test_diff_stats__shortstat(void) git_buf_free(&buf); } +void test_diff_stats__shortstat_noinsertions(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " 1 file changed, 2 deletions(-)\n"; + + diff_stats_from_commit_oid( + &_stats, "06b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(0, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(2, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); + cl_assert_equal_s(stat, git_buf_cstr(&buf)); + git_buf_free(&buf); +} + +void test_diff_stats__shortstat_nodeletions(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *stat = + " 1 file changed, 3 insertions(+)\n"; + + diff_stats_from_commit_oid( + &_stats, "5219b9784f9a92d7bd7cb567a6d6a21bfb86697e", false); + + cl_assert_equal_sz(1, git_diff_stats_files_changed(_stats)); + cl_assert_equal_sz(3, git_diff_stats_insertions(_stats)); + cl_assert_equal_sz(0, git_diff_stats_deletions(_stats)); + + cl_git_pass(git_diff_stats_to_buf(&buf, _stats, GIT_DIFF_STATS_SHORT, 0)); + cl_assert_equal_s(stat, git_buf_cstr(&buf)); + git_buf_free(&buf); +} + void test_diff_stats__rename(void) { git_buf buf = GIT_BUF_INIT; diff --git a/tests/diff/submodules.c b/tests/diff/submodules.c index 08682cd4b..eebfef3a2 100644 --- a/tests/diff/submodules.c +++ b/tests/diff/submodules.c @@ -493,3 +493,50 @@ void test_diff_submodules__skips_empty_includes_used(void) cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); git_diff_free(diff); } + +static void ensure_submodules_found( + git_repository *repo, + const char **paths, + size_t cnt) +{ + git_diff *diff = NULL; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + const git_diff_delta *delta; + size_t i, pathlen; + + opts.pathspec.strings = (char **)paths; + opts.pathspec.count = cnt; + + git_diff_index_to_workdir(&diff, repo, NULL, &opts); + + cl_assert_equal_i(cnt, git_diff_num_deltas(diff)); + + for (i = 0; i < cnt; i++) { + delta = git_diff_get_delta(diff, i); + + /* ensure that the given path is returned w/o trailing slashes. */ + pathlen = strlen(opts.pathspec.strings[i]); + + while (pathlen && opts.pathspec.strings[i][pathlen - 1] == '/') + pathlen--; + + cl_assert_equal_strn(opts.pathspec.strings[i], delta->new_file.path, pathlen); + } + + git_diff_free(diff); +} + +void test_diff_submodules__can_be_identified_by_trailing_slash_in_pathspec(void) +{ + const char *one_path_without_slash[] = { "sm_changed_head" }; + const char *one_path_with_slash[] = { "sm_changed_head/" }; + const char *many_paths_without_slashes[] = { "sm_changed_head", "sm_changed_index" }; + const char *many_paths_with_slashes[] = { "sm_changed_head/", "sm_changed_index/" }; + + g_repo = setup_fixture_submod2(); + + ensure_submodules_found(g_repo, one_path_without_slash, ARRAY_SIZE(one_path_without_slash)); + ensure_submodules_found(g_repo, one_path_with_slash, ARRAY_SIZE(one_path_with_slash)); + ensure_submodules_found(g_repo, many_paths_without_slashes, ARRAY_SIZE(many_paths_without_slashes)); + ensure_submodules_found(g_repo, many_paths_with_slashes, ARRAY_SIZE(many_paths_with_slashes)); +} diff --git a/tests/index/collision.c b/tests/index/collision.c index ad5827e0b..41eb7bfdc 100644 --- a/tests/index/collision.c +++ b/tests/index/collision.c @@ -79,8 +79,6 @@ void test_index_collision__add_with_highstage_2(void) { git_index_entry entry; - cl_git_pass(git_repository_index(&g_index, g_repo)); - memset(&entry, 0, sizeof(entry)); entry.ctime.seconds = 12346789; entry.mtime.seconds = 12346789; diff --git a/tests/index/conflicts.c b/tests/index/conflicts.c index d4004686f..cabef888c 100644 --- a/tests/index/conflicts.c +++ b/tests/index/conflicts.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "index.h" #include "git2/repository.h" +#include "conflicts.h" static git_repository *repo; static git_index *repo_index; @@ -8,14 +9,6 @@ static git_index *repo_index; #define TEST_REPO_PATH "mergedrepo" #define TEST_INDEX_PATH TEST_REPO_PATH "/.git/index" -#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" -#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" -#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" - -#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" -#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" -#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" - // Fixture setup and teardown void test_index_conflicts__initialize(void) { diff --git a/tests/index/conflicts.h b/tests/index/conflicts.h new file mode 100644 index 000000000..3e26e6d5b --- /dev/null +++ b/tests/index/conflicts.h @@ -0,0 +1,7 @@ +#define CONFLICTS_ONE_ANCESTOR_OID "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81" +#define CONFLICTS_ONE_OUR_OID "6aea5f295304c36144ad6e9247a291b7f8112399" +#define CONFLICTS_ONE_THEIR_OID "516bd85f78061e09ccc714561d7b504672cb52da" + +#define CONFLICTS_TWO_ANCESTOR_OID "84af62840be1b1c47b778a8a249f3ff45155038c" +#define CONFLICTS_TWO_OUR_OID "8b3f43d2402825c200f835ca1762413e386fd0b2" +#define CONFLICTS_TWO_THEIR_OID "220bd62631c8cf7a83ef39c6b94595f00517211e" diff --git a/tests/index/nsec.c b/tests/index/nsec.c index 244ab6362..ea8a26743 100644 --- a/tests/index/nsec.c +++ b/tests/index/nsec.c @@ -24,6 +24,43 @@ void test_index_nsec__cleanup(void) cl_git_sandbox_cleanup(); } +static bool try_create_file_with_nsec_timestamp(const char *path) +{ + struct stat st; + int try; + + /* retry a few times to avoid nanos *actually* equal 0 race condition */ + for (try = 0; try < 3; try++) { + cl_git_mkfile(path, "This is hopefully a file with nanoseconds!"); + + cl_must_pass(p_stat(path, &st)); + + if (st.st_ctime_nsec && st.st_mtime_nsec) + return true; + } + + return false; +} + +/* try to determine if the underlying filesystem supports a resolution + * higher than a single second. (i'm looking at you, hfs+) + */ +static bool should_expect_nsecs(void) +{ + git_buf nsec_path = GIT_BUF_INIT; + bool expect; + + git_buf_joinpath(&nsec_path, clar_sandbox_path(), "nsec_test"); + + expect = try_create_file_with_nsec_timestamp(nsec_path.ptr); + + p_unlink(nsec_path.ptr); + + git_buf_free(&nsec_path); + + return expect; +} + static bool has_nsecs(void) { const git_index_entry *entry; @@ -50,8 +87,13 @@ void test_index_nsec__has_nanos(void) void test_index_nsec__staging_maintains_other_nanos(void) { const git_index_entry *entry; + bool expect_nsec, test_file_has_nsec; + + expect_nsec = should_expect_nsecs(); + test_file_has_nsec = try_create_file_with_nsec_timestamp("nsecs/a.txt"); + + cl_assert_equal_b(expect_nsec, test_file_has_nsec); - cl_git_rewritefile("nsecs/a.txt", "This is file A"); cl_git_pass(git_index_add_bypath(repo_index, "a.txt")); cl_git_pass(git_index_write(repo_index)); @@ -63,15 +105,15 @@ void test_index_nsec__staging_maintains_other_nanos(void) cl_assert((entry = git_index_get_bypath(repo_index, "a.txt", 0))); /* if we are writing nanoseconds to the index, expect them to be - * nonzero. if we are *not*, expect that we truncated the entry. + * nonzero. */ -#ifdef GIT_USE_NSEC - cl_assert(entry->ctime.nanoseconds != 0); - cl_assert(entry->mtime.nanoseconds != 0); -#else - cl_assert_equal_i(0, entry->ctime.nanoseconds); - cl_assert_equal_i(0, entry->mtime.nanoseconds); -#endif + if (expect_nsec) { + cl_assert(entry->ctime.nanoseconds != 0); + cl_assert(entry->mtime.nanoseconds != 0); + } else { + cl_assert_equal_i(0, entry->ctime.nanoseconds); + cl_assert_equal_i(0, entry->mtime.nanoseconds); + } } void test_index_nsec__status_doesnt_clear_nsecs(void) diff --git a/tests/index/read_index.c b/tests/index/read_index.c index 6d14bc9fd..2df7cc8eb 100644 --- a/tests/index/read_index.c +++ b/tests/index/read_index.c @@ -1,6 +1,7 @@ #include "clar_libgit2.h" #include "posix.h" #include "index.h" +#include "conflicts.h" static git_repository *_repo; static git_index *_index; @@ -126,3 +127,106 @@ void test_index_read_index__read_and_writes(void) git_index_free(tree_index); git_index_free(new_index); } + +static void add_conflicts(git_index *index, const char *filename) +{ + git_index_entry ancestor_entry, our_entry, their_entry; + static int conflict_idx = 0; + char *ancestor_ids[] = + { CONFLICTS_ONE_ANCESTOR_OID, CONFLICTS_TWO_ANCESTOR_OID }; + char *our_ids[] = + { CONFLICTS_ONE_OUR_OID, CONFLICTS_TWO_OUR_OID }; + char *their_ids[] = + { CONFLICTS_ONE_THEIR_OID, CONFLICTS_TWO_THEIR_OID }; + + conflict_idx = (conflict_idx + 1) % 2; + + memset(&ancestor_entry, 0x0, sizeof(git_index_entry)); + memset(&our_entry, 0x0, sizeof(git_index_entry)); + memset(&their_entry, 0x0, sizeof(git_index_entry)); + + ancestor_entry.path = filename; + ancestor_entry.mode = 0100644; + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 1); + git_oid_fromstr(&ancestor_entry.id, ancestor_ids[conflict_idx]); + + our_entry.path = filename; + our_entry.mode = 0100644; + GIT_IDXENTRY_STAGE_SET(&our_entry, 2); + git_oid_fromstr(&our_entry.id, our_ids[conflict_idx]); + + their_entry.path = filename; + their_entry.mode = 0100644; + GIT_IDXENTRY_STAGE_SET(&ancestor_entry, 2); + git_oid_fromstr(&their_entry.id, their_ids[conflict_idx]); + + cl_git_pass(git_index_conflict_add(index, &ancestor_entry, + &our_entry, &their_entry)); +} + +void test_index_read_index__handles_conflicts(void) +{ + git_oid tree_id; + git_tree *tree; + git_index *index, *new_index; + git_index_conflict_iterator *conflict_iterator; + const git_index_entry *ancestor, *ours, *theirs; + + cl_git_pass(git_oid_fromstr(&tree_id, "ae90f12eea699729ed24555e40b9fd669da12a12")); + cl_git_pass(git_tree_lookup(&tree, _repo, &tree_id)); + cl_git_pass(git_index_new(&index)); + cl_git_pass(git_index_new(&new_index)); + cl_git_pass(git_index_read_tree(index, tree)); + cl_git_pass(git_index_read_tree(new_index, tree)); + + /* put some conflicts in only the old side, these should be removed */ + add_conflicts(index, "orig_side-1.txt"); + add_conflicts(index, "orig_side-2.txt"); + + /* put some conflicts in both indexes, these should be unchanged */ + add_conflicts(index, "both_sides-1.txt"); + add_conflicts(new_index, "both_sides-1.txt"); + add_conflicts(index, "both_sides-2.txt"); + add_conflicts(new_index, "both_sides-2.txt"); + + /* put some conflicts in the new index, these should be added */ + add_conflicts(new_index, "new_side-1.txt"); + add_conflicts(new_index, "new_side-2.txt"); + + cl_git_pass(git_index_read_index(index, new_index)); + cl_git_pass(git_index_conflict_iterator_new(&conflict_iterator, index)); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("both_sides-1.txt", ancestor->path); + cl_assert_equal_s("both_sides-1.txt", ours->path); + cl_assert_equal_s("both_sides-1.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("both_sides-2.txt", ancestor->path); + cl_assert_equal_s("both_sides-2.txt", ours->path); + cl_assert_equal_s("both_sides-2.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("new_side-1.txt", ancestor->path); + cl_assert_equal_s("new_side-1.txt", ours->path); + cl_assert_equal_s("new_side-1.txt", theirs->path); + + cl_git_pass(git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + cl_assert_equal_s("new_side-2.txt", ancestor->path); + cl_assert_equal_s("new_side-2.txt", ours->path); + cl_assert_equal_s("new_side-2.txt", theirs->path); + + + cl_git_fail_with(GIT_ITEROVER, git_index_conflict_next( + &ancestor, &ours, &theirs, conflict_iterator)); + + git_index_conflict_iterator_free(conflict_iterator); + + git_tree_free(tree); + git_index_free(new_index); + git_index_free(index); +} diff --git a/tests/index/version.c b/tests/index/version.c new file mode 100644 index 000000000..3fd240d3c --- /dev/null +++ b/tests/index/version.c @@ -0,0 +1,41 @@ +#include "clar_libgit2.h" +#include "index.h" + +static git_repository *g_repo = NULL; + +void test_index_version__can_write_v4(void) +{ + git_index *index; + const git_index_entry *entry; + + g_repo = cl_git_sandbox_init("filemodes"); + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_assert(index->on_disk); + cl_assert(git_index_version(index) == 2); + + cl_assert(git_index_entrycount(index) == 6); + + cl_git_pass(git_index_set_version(index, 4)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_assert(git_index_version(index) == 4); + + entry = git_index_get_bypath(index, "exec_off", 0); + cl_assert(entry); + entry = git_index_get_bypath(index, "exec_off2on_staged", 0); + cl_assert(entry); + entry = git_index_get_bypath(index, "exec_on", 0); + cl_assert(entry); + + git_index_free(index); +} + +void test_index_version__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} diff --git a/tests/iterator/index.c b/tests/iterator/index.c new file mode 100644 index 000000000..b609d5990 --- /dev/null +++ b/tests/iterator/index.c @@ -0,0 +1,1385 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "fileops.h" +#include "iterator_helpers.h" +#include "../submodule/submodule_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_index__initialize(void) +{ +} + +void test_iterator_index__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void index_iterator_test( + const char *sandbox, + const char *start, + const char *end, + git_iterator_flag_t flags, + int expected_count, + const char **expected_names, + const char **expected_oids) +{ + git_index *index; + git_iterator *i; + const git_index_entry *entry; + int error, count = 0, caps; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init(sandbox); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + iter_opts.flags = flags; + iter_opts.start = start; + iter_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &iter_opts)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count], entry->path); + + if (expected_oids != NULL) { + git_oid oid; + cl_git_pass(git_oid_fromstr(&oid, expected_oids[count])); + cl_assert_equal_oid(&oid, &entry->id); + } + + count++; + } + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + + cl_assert(caps == git_index_caps(index)); + git_index_free(index); +} + +static const char *expected_index_0[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", +}; + +static const char *expected_index_oids_0[] = { + "556f8c827b8e4a02ad5cab77dca2bcb3e226b0b3", + "3b74db7ab381105dc0d28f8295a77f6a82989292", + "2c66e14f77196ea763fb1e41612c1aa2bc2d8ed2", + "c485abe35abd4aa6fd83b076a78bbea9e2e7e06c", + "d800886d9c86731ae5c4a62b0b77c437015e00d2", + "2b40c5aca159b04ea8d20ffe36cdf8b09369b14a", + "5819a185d77b03325aaf87cafc771db36f6ddca7", + "ff69f8639ce2e6010b3f33a74160aad98b48da2b", + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "9e5bdc47d6a80f2be0ea3049ad74231b94609242", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "3e42ffc54a663f9401cc25843d6c0e71a33e4249", + "e563cf4758f0d646f1b14b76016aa17fa9e549a4", + "fb5067b1aef3ac1ada4b379dbcb7d17255df7d78", + "dccada462d3df8ac6de596fb8c896aba9344f941" +}; + +void test_iterator_index__0(void) +{ + index_iterator_test( + "attr", NULL, NULL, 0, ARRAY_SIZE(expected_index_0), + expected_index_0, expected_index_oids_0); +} + +static const char *expected_index_1[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_new_file", + "staged_new_file_deleted_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", +}; + +static const char* expected_index_oids_1[] = { + "a0de7e0ac200c489c41c59dfa910154a70264e6e", + "5452d32f1dd538eb0405e8a83cc185f79e25e80f", + "452e4244b5d083ddf0460acf1ecc74db9dcfa11a", + "55d316c9ba708999f1918e9677d01dfcae69c6b9", + "a6be623522ce87a1d862128ac42672604f7b468b", + "906ee7711f4f4928ddcb2a5f8fbc500deba0d2a8", + "529a16e8e762d4acb7b9636ff540a00831f9155a", + "90b8c29d8ba39434d1c63e1b093daaa26e5bd972", + "ed062903b8f6f3dccb2fa81117ba6590944ef9bd", + "e8ee89e15bbe9b20137715232387b3de5b28972e", + "53ace0d1cc1145a5f4fe4f78a186a60263190733", + "1888c805345ba265b0ee9449b8877b6064592058", + "a6191982709b746d5650e93c2acf34ef74e11504" +}; + +void test_iterator_index__1(void) +{ + index_iterator_test( + "status", NULL, NULL, 0, ARRAY_SIZE(expected_index_1), + expected_index_1, expected_index_oids_1); +} + +static const char *expected_index_range[] = { + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", +}; + +static const char *expected_index_oids_range[] = { + "45141a79a77842c59a63229403220a4e4be74e3d", + "4d713dc48e6b1bd75b0d61ad078ba9ca3a56745d", + "108bb4e7fd7b16490dc33ff7d972151e73d7166e", + "a0f7217ae99f5ac3e88534f5cea267febc5fa85b", +}; + +void test_iterator_index__range(void) +{ + index_iterator_test( + "attr", "root", "root", 0, ARRAY_SIZE(expected_index_range), + expected_index_range, expected_index_oids_range); +} + +void test_iterator_index__range_empty_0(void) +{ + index_iterator_test( + "attr", "empty", "empty", 0, 0, NULL, NULL); +} + +void test_iterator_index__range_empty_1(void) +{ + index_iterator_test( + "attr", "z_empty_after", NULL, 0, 0, NULL, NULL); +} + +void test_iterator_index__range_empty_2(void) +{ + index_iterator_test( + "attr", NULL, ".aaa_empty_before", 0, 0, NULL, NULL); +} + +static void check_index_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_index *index; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, count, caps; + bool is_ignoring_case; + + cl_git_pass(git_repository_index(&index, repo)); + + caps = git_index_caps(index); + is_ignoring_case = ((caps & GIT_INDEXCAP_IGNORE_CASE) != 0); + + if (ignore_case != is_ignoring_case) + cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE)); + + i_opts.flags = 0; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, repo, index, &i_opts)); + + cl_assert(git_iterator_ignore_case(i) == ignore_case); + + for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) + /* count em up */; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__range_icase(void) +{ + git_index *index; + git_tree *head; + + g_repo = cl_git_sandbox_init("testrepo"); + + /* reset index to match HEAD */ + cl_git_pass(git_repository_head_tree(&head, g_repo)); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_read_tree(index, head)); + cl_git_pass(git_index_write(index)); + git_tree_free(head); + git_index_free(index); + + /* do some ranged iterator checks toggling case sensitivity */ + check_index_range(g_repo, "B", "C", false, 0); + check_index_range(g_repo, "B", "C", true, 1); + check_index_range(g_repo, "a", "z", false, 3); + check_index_range(g_repo, "a", "z", true, 4); +} + +static const char *expected_index_cs[] = { + "B", "D", "F", "H", "J", "L/1", "L/B", "L/D", "L/a", "L/c", + "a", "c", "e", "g", "i", "k/1", "k/B", "k/D", "k/a", "k/c", +}; + +static const char *expected_index_ci[] = { + "a", "B", "c", "D", "e", "F", "g", "H", "i", "J", + "k/1", "k/a", "k/B", "k/c", "k/D", "L/1", "L/a", "L/B", "L/c", "L/D", +}; + +void test_iterator_index__case_folding(void) +{ + git_buf path = GIT_BUF_INIT; + int fs_is_ci = 0; + + cl_git_pass(git_buf_joinpath(&path, cl_fixture("icase"), ".gitted/CoNfIg")); + fs_is_ci = git_path_exists(path.ptr); + git_buf_free(&path); + + index_iterator_test( + "icase", NULL, NULL, 0, ARRAY_SIZE(expected_index_cs), + fs_is_ci ? expected_index_ci : expected_index_cs, NULL); + + cl_git_sandbox_cleanup(); + + index_iterator_test( + "icase", NULL, NULL, GIT_ITERATOR_IGNORE_CASE, + ARRAY_SIZE(expected_index_ci), expected_index_ci, NULL); + + cl_git_sandbox_cleanup(); + + index_iterator_test( + "icase", NULL, NULL, GIT_ITERATOR_DONT_IGNORE_CASE, + ARRAY_SIZE(expected_index_cs), expected_index_cs, NULL); +} + +/* Index contents (including pseudotrees): + * + * 0: a 5: F 10: k/ 16: L/ + * 1: B 6: g 11: k/1 17: L/1 + * 2: c 7: H 12: k/a 18: L/a + * 3: D 8: i 13: k/B 19: L/B + * 4: e 9: J 14: k/c 20: L/c + * 15: k/D 21: L/D + * + * 0: B 5: L/ 11: a 16: k/ + * 1: D 6: L/1 12: c 17: k/1 + * 2: F 7: L/B 13: e 18: k/B + * 3: H 8: L/D 14: g 19: k/D + * 4: J 9: L/a 15: i 20: k/a + * 10: L/c 21: k/c + */ + +void test_iterator_index__icase_0(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* autoexpand with no tree entries for index */ + cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); + + git_index_free(index); +} + +void test_iterator_index__icase_1(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + int caps; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + /* force case sensitivity */ + cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); + + /* autoexpand with no tree entries over range */ + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* force case insensitivity */ + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); + + /* autoexpand with no tree entries over range */ + i_opts.flags = 0; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); + + cl_git_pass(git_index_set_caps(index, caps)); + git_index_free(index); +} + +void test_iterator_index__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + /* Case sensitive */ + { + const char *expected[] = { + "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Case INsensitive */ + { + const char *expected[] = { + "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case sensitive. */ + { + const char *expected[] = { "c", "e", "k/1", "k/a" }; + size_t expected_len = 4; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case INsensitive. */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 6; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case sensitive. */ + { + const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; + size_t expected_len = 6; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case INsensitive. */ + { + const char *expected[] = { "a", "B", "c", "D", "e" }; + size_t expected_len = 5; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "c", "e", "k/1" }; + size_t expected_len = 3; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "k/1" }; + size_t expected_len = 1; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; + size_t expected_len = 5; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "k/1", "k/a" }; + size_t expected_len = 2; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_dirs(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + /* Test that a prefix `k` matches folders, even without trailing slash */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a `k/` matches a folder */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* When the iterator is case sensitive, ensure we can't lookup the + * directory with the wrong case. + */ + { + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + } + + /* Test that case insensitive matching works. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that case insensitive matching works without trailing slash. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_dirs_include_trees(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + const char *expected[] = { "k/", "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 6; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_1(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */ + expect = default_icase ? 5 : 3; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_2(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_four(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist = GIT_VECTOR_INIT; + int default_icase, expect; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "0")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + + /* In this test we DO NOT force a case setting on the index. */ + default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ + expect = default_icase ? 8 : 5; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + int caps; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_index(&index, g_repo)); + caps = git_index_caps(index); + + /* force case sensitivity */ + cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + /* force case insensitivity */ + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + cl_git_pass(git_index_set_caps(index, caps)); + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + git_index *index; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + git_index_free(index); + git_tree_free(tree); + git_vector_free(&filelist); +} + +static void create_paths(git_index *index, const char *root, int depth) +{ + git_buf fullpath = GIT_BUF_INIT; + git_index_entry entry; + size_t root_len; + int i; + + if (root) { + cl_git_pass(git_buf_puts(&fullpath, root)); + cl_git_pass(git_buf_putc(&fullpath, '/')); + } + + root_len = fullpath.size; + + for (i = 0; i < 8; i++) { + bool file = (depth == 0 || (i % 2) == 0); + git_buf_truncate(&fullpath, root_len); + cl_git_pass(git_buf_printf(&fullpath, "item%d", i)); + + if (file) { + memset(&entry, 0, sizeof(git_index_entry)); + entry.path = fullpath.ptr; + entry.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&entry.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + + cl_git_pass(git_index_add(index, &entry)); + } else if (depth > 0) { + create_paths(index, fullpath.ptr, (depth - 1)); + } + } + + git_buf_free(&fullpath); +} + +void test_iterator_index__pathlist_for_deeply_nested_item(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 3); + + /* Ensure that we find the single path we're interested in */ + { + const char *expected[] = { "item1/item3/item5/item7" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + { + const char *expected[] = { + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + }; + size_t expected_len = 8; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + { + const char *expected[] = { + "item1/item3/item0", + "item1/item3/item1/item0", "item1/item3/item1/item1", + "item1/item3/item1/item2", "item1/item3/item1/item3", + "item1/item3/item1/item4", "item1/item3/item1/item5", + "item1/item3/item1/item6", "item1/item3/item1/item7", + "item1/item3/item2", + "item1/item3/item3/item0", "item1/item3/item3/item1", + "item1/item3/item3/item2", "item1/item3/item3/item3", + "item1/item3/item3/item4", "item1/item3/item3/item5", + "item1/item3/item3/item6", "item1/item3/item3/item7", + "item1/item3/item4", + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + "item1/item3/item6", + "item1/item3/item7/item0", "item1/item3/item7/item1", + "item1/item3/item7/item2", "item1/item3/item7/item3", + "item1/item3/item7/item4", "item1/item3/item7/item5", + "item1/item3/item7/item6", "item1/item3/item7/item7", + }; + size_t expected_len = 36; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item0", "item1/item2", "item5/item7/item4", "item6", + "item7/item3/item1/item6" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); + cl_git_pass(git_vector_insert(&filelist, "item6")); + cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); + cl_git_pass(git_vector_insert(&filelist, "item1/item2")); + cl_git_pass(git_vector_insert(&filelist, "item0")); + + /* also add some things that don't exist or don't match the right type */ + cl_git_pass(git_vector_insert(&filelist, "item2/")); + cl_git_pass(git_vector_insert(&filelist, "itemN")); + cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); + cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_index_free(index); + git_vector_free(&filelist); +} + +void test_iterator_index__advance_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 1); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item0", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item2", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__advance_into(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +void test_iterator_index__advance_into_and_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_repository_index(&index, g_repo)); + + create_paths(index, NULL, 2); + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "item0"); + expect_advance_into(i, "item1/"); + expect_advance_into(i, "item1/item0"); + expect_advance_into(i, "item1/item1/"); + expect_advance_into(i, "item1/item1/item0"); + expect_advance_into(i, "item1/item1/item1"); + expect_advance_into(i, "item1/item1/item2"); + expect_advance_into(i, "item1/item1/item3"); + expect_advance_into(i, "item1/item1/item4"); + expect_advance_into(i, "item1/item1/item5"); + expect_advance_into(i, "item1/item1/item6"); + expect_advance_into(i, "item1/item1/item7"); + expect_advance_into(i, "item1/item2"); + expect_advance_over(i, "item1/item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item1/item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_into(i, "item2"); + expect_advance_over(i, "item3/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item5/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item6", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "item7/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_index_free(index); +} + +static void add_conflict( + git_index *index, + const char *ancestor_path, + const char *our_path, + const char *their_path) +{ + git_index_entry ancestor = {{0}}, ours = {{0}}, theirs = {{0}}; + + ancestor.path = ancestor_path; + ancestor.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&ancestor.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_IDXENTRY_STAGE_SET(&ancestor, 1); + + ours.path = our_path; + ours.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&ours.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_IDXENTRY_STAGE_SET(&ours, 2); + + theirs.path = their_path; + theirs.mode = GIT_FILEMODE_BLOB; + git_oid_fromstr(&theirs.id, "d44e18fb93b7107b5cd1b95d601591d77869a1b6"); + GIT_IDXENTRY_STAGE_SET(&theirs, 3); + + cl_git_pass(git_index_conflict_add(index, &ancestor, &ours, &theirs)); +} + +void test_iterator_index__include_conflicts(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_index *index; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + cl_git_pass(git_repository_index(&index, g_repo)); + + add_conflict(index, "CONFLICT1", "CONFLICT1" ,"CONFLICT1"); + add_conflict(index, "ZZZ-CONFLICT2.ancestor", "ZZZ-CONFLICT2.ours", "ZZZ-CONFLICT2.theirs"); + add_conflict(index, "ancestor.conflict3", "ours.conflict3", "theirs.conflict3"); + add_conflict(index, "zzz-conflict4", "zzz-conflict4", "zzz-conflict4"); + + /* Iterate the index, ensuring that conflicts are not included */ + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + + /* Try again, returning conflicts */ + i_opts.flags |= GIT_ITERATOR_INCLUDE_CONFLICTS; + + cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "CONFLICT1", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.ancestor", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.ours", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ZZZ-CONFLICT2.theirs", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ancestor.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "ours.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "theirs.conflict3", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "zzz-conflict4", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + + git_index_free(index); +} diff --git a/tests/iterator/iterator_helpers.c b/tests/iterator/iterator_helpers.c new file mode 100644 index 000000000..ae48fcd46 --- /dev/null +++ b/tests/iterator/iterator_helpers.c @@ -0,0 +1,144 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "fileops.h" +#include "iterator_helpers.h" +#include + +static void assert_at_end(git_iterator *i, bool verbose) +{ + const git_index_entry *end; + int error = git_iterator_advance(&end, i); + + if (verbose && error != GIT_ITEROVER) + fprintf(stderr, "Expected end of iterator, got '%s'\n", end->path); + + cl_git_fail_with(GIT_ITEROVER, error); +} + +void expect_iterator_items( + git_iterator *i, + size_t expected_flat, + const char **expected_flat_paths, + size_t expected_total, + const char **expected_total_paths) +{ + const git_index_entry *entry; + size_t count; + int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); + bool v = false; + int error; + + if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees"); + + count = 0; + + while (!git_iterator_advance(&entry, i)) { + if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); + + if (no_trees) + cl_assert(entry->mode != GIT_FILEMODE_TREE); + + if (expected_flat_paths) { + const char *expect_path = expected_flat_paths[count]; + size_t expect_len = strlen(expect_path); + + cl_assert_equal_s(expect_path, entry->path); + + if (expect_path[expect_len - 1] == '/') + cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); + else + cl_assert(entry->mode != GIT_FILEMODE_TREE); + } + + if (++count >= expected_flat) + break; + } + + assert_at_end(i, v); + cl_assert_equal_i(expected_flat, count); + + cl_git_pass(git_iterator_reset(i)); + + count = 0; + cl_git_pass(git_iterator_current(&entry, i)); + + if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees"); + + while (entry != NULL) { + if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); + + if (no_trees) + cl_assert(entry->mode != GIT_FILEMODE_TREE); + + if (expected_total_paths) { + const char *expect_path = expected_total_paths[count]; + size_t expect_len = strlen(expect_path); + + cl_assert_equal_s(expect_path, entry->path); + + if (expect_path[expect_len - 1] == '/') + cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); + else + cl_assert(entry->mode != GIT_FILEMODE_TREE); + } + + if (entry->mode == GIT_FILEMODE_TREE) { + error = git_iterator_advance_into(&entry, i); + + /* could return NOTFOUND if directory is empty */ + cl_assert(!error || error == GIT_ENOTFOUND); + + if (error == GIT_ENOTFOUND) { + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + } else { + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + + if (++count >= expected_total) + break; + } + + assert_at_end(i, v); + cl_assert_equal_i(expected_total, count); +} + + +void expect_advance_over( + git_iterator *i, + const char *expected_path, + git_iterator_status_t expected_status) +{ + const git_index_entry *entry; + git_iterator_status_t status; + int error; + + cl_git_pass(git_iterator_current(&entry, i)); + cl_assert_equal_s(expected_path, entry->path); + + error = git_iterator_advance_over(&entry, &status, i); + cl_assert(!error || error == GIT_ITEROVER); + cl_assert_equal_i(expected_status, status); +} + +void expect_advance_into( + git_iterator *i, + const char *expected_path) +{ + const git_index_entry *entry; + int error; + + cl_git_pass(git_iterator_current(&entry, i)); + cl_assert_equal_s(expected_path, entry->path); + + if (S_ISDIR(entry->mode)) + error = git_iterator_advance_into(&entry, i); + else + error = git_iterator_advance(&entry, i); + + cl_assert(!error || error == GIT_ITEROVER); +} + diff --git a/tests/iterator/iterator_helpers.h b/tests/iterator/iterator_helpers.h new file mode 100644 index 000000000..1884b41a1 --- /dev/null +++ b/tests/iterator/iterator_helpers.h @@ -0,0 +1,16 @@ + +extern void expect_iterator_items( + git_iterator *i, + size_t expected_flat, + const char **expected_flat_paths, + size_t expected_total, + const char **expected_total_paths); + +extern void expect_advance_over( + git_iterator *i, + const char *expected_path, + git_iterator_status_t expected_status); + +void expect_advance_into( + git_iterator *i, + const char *expected_path); diff --git a/tests/iterator/tree.c b/tests/iterator/tree.c new file mode 100644 index 000000000..07da58371 --- /dev/null +++ b/tests/iterator/tree.c @@ -0,0 +1,1080 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "fileops.h" +#include "tree.h" +#include "../submodule/submodule_helpers.h" +#include "../diff/diff_helpers.h" +#include "iterator_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_tree__initialize(void) +{ +} + +void test_iterator_tree__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void tree_iterator_test( + const char *sandbox, + const char *treeish, + const char *start, + const char *end, + int expected_count, + const char **expected_values) +{ + git_tree *t; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, count = 0, count_post_reset = 0; + + g_repo = cl_git_sandbox_init(sandbox); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_assert(t = resolve_commit_oid_to_tree(g_repo, treeish)); + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); + + /* test loop */ + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count], entry->path); + count++; + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(expected_count, count); + + /* test reset */ + cl_git_pass(git_iterator_reset(i)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + if (expected_values != NULL) + cl_assert_equal_s(expected_values[count_post_reset], entry->path); + count_post_reset++; + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(count, count_post_reset); + + git_iterator_free(i); + git_tree_free(t); +} + +/* results of: git ls-tree -r --name-only 605812a */ +const char *expected_tree_0[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__0(void) +{ + tree_iterator_test("attr", "605812a", NULL, NULL, 16, expected_tree_0); +} + +/* results of: git ls-tree -r --name-only 6bab5c79 */ +const char *expected_tree_1[] = { + ".gitattributes", + "attr0", + "attr1", + "attr2", + "attr3", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "subdir/.gitattributes", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__1(void) +{ + tree_iterator_test("attr", "6bab5c79cd5", NULL, NULL, 13, expected_tree_1); +} + +/* results of: git ls-tree -r --name-only 26a125ee1 */ +const char *expected_tree_2[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL +}; + +void test_iterator_tree__2(void) +{ + tree_iterator_test("status", "26a125ee1", NULL, NULL, 12, expected_tree_2); +} + +/* $ git ls-tree -r --name-only 0017bd4ab1e */ +const char *expected_tree_3[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file" +}; + +void test_iterator_tree__3(void) +{ + tree_iterator_test("status", "0017bd4ab1e", NULL, NULL, 8, expected_tree_3); +} + +/* $ git ls-tree -r --name-only 24fa9a9fc4e202313e24b648087495441dab432b */ +const char *expected_tree_4[] = { + "attr0", + "attr1", + "attr2", + "attr3", + "binfile", + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + "sub/abc", + "sub/file", + "sub/sub/file", + "sub/sub/subsub.txt", + "sub/subdir_test1", + "sub/subdir_test2.txt", + "subdir/.gitattributes", + "subdir/abc", + "subdir/subdir_test1", + "subdir/subdir_test2.txt", + "subdir2/subdir2_test1", + NULL +}; + +void test_iterator_tree__4(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", NULL, NULL, + 23, expected_tree_4); +} + +void test_iterator_tree__4_ranged(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub", "sub", + 11, &expected_tree_4[12]); +} + +const char *expected_tree_ranged_0[] = { + "gitattributes", + "macro_bad", + "macro_test", + "root_test1", + "root_test2", + "root_test3", + "root_test4.txt", + NULL +}; + +void test_iterator_tree__ranged_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "git", "root", + 7, expected_tree_ranged_0); +} + +const char *expected_tree_ranged_1[] = { + "sub/subdir_test2.txt", + NULL +}; + +void test_iterator_tree__ranged_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "sub/subdir_test2.txt", "sub/subdir_test2.txt", + 1, expected_tree_ranged_1); +} + +void test_iterator_tree__range_empty_0(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "empty", "empty", 0, NULL); +} + +void test_iterator_tree__range_empty_1(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + "z_empty_after", NULL, 0, NULL); +} + +void test_iterator_tree__range_empty_2(void) +{ + tree_iterator_test( + "attr", "24fa9a9fc4e202313e24b648087495441dab432b", + NULL, ".aaa_empty_before", 0, NULL); +} + +static void check_tree_entry( + git_iterator *i, + const char *oid, + const char *oid_p, + const char *oid_pp, + const char *oid_ppp) +{ + const git_index_entry *ie; + const git_tree_entry *te; + const git_tree *tree; + + cl_git_pass(git_iterator_current_tree_entry(&te, i)); + cl_assert(te); + cl_assert(git_oid_streq(te->oid, oid) == 0); + + cl_git_pass(git_iterator_current(&ie, i)); + + if (oid_p) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 0)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_p) == 0); + } + + if (oid_pp) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 1)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_pp) == 0); + } + + if (oid_ppp) { + cl_git_pass(git_iterator_current_parent_tree(&tree, i, 2)); + cl_assert(tree); + cl_assert(git_oid_streq(git_tree_id(tree), oid_ppp) == 0); + } +} + +void test_iterator_tree__special_functions(void) +{ + git_tree *t; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, cases = 0; + const char *rootoid = "ce39a97a7fb1fa90bcf5e711249c1e507476ae0e"; + + g_repo = cl_git_sandbox_init("attr"); + + t = resolve_commit_oid_to_tree( + g_repo, "24fa9a9fc4e202313e24b648087495441dab432b"); + cl_assert(t != NULL); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); + + while (!(error = git_iterator_advance(&entry, i))) { + cl_assert(entry); + + if (strcmp(entry->path, "sub/file") == 0) { + cases++; + check_tree_entry( + i, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057", + "ecb97df2a174987475ac816e3847fc8e9f6c596b", + rootoid, NULL); + } + else if (strcmp(entry->path, "sub/sub/subsub.txt") == 0) { + cases++; + check_tree_entry( + i, "9e5bdc47d6a80f2be0ea3049ad74231b94609242", + "4e49ba8c5b6c32ff28cd9dcb60be34df50fcc485", + "ecb97df2a174987475ac816e3847fc8e9f6c596b", rootoid); + } + else if (strcmp(entry->path, "subdir/.gitattributes") == 0) { + cases++; + check_tree_entry( + i, "99eae476896f4907224978b88e5ecaa6c5bb67a9", + "9fb40b6675dde60b5697afceae91b66d908c02d9", + rootoid, NULL); + } + else if (strcmp(entry->path, "subdir2/subdir2_test1") == 0) { + cases++; + check_tree_entry( + i, "dccada462d3df8ac6de596fb8c896aba9344f941", + "2929de282ce999e95183aedac6451d3384559c4b", + rootoid, NULL); + } + } + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert(!entry); + cl_assert_equal_i(4, cases); + + git_iterator_free(i); + git_tree_free(t); +} + +static void check_tree_range( + git_repository *repo, + const char *start, + const char *end, + bool ignore_case, + int expected_count) +{ + git_tree *head; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + int error, count; + + i_opts.flags = ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_repository_head_tree(&head, repo)); + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + + for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) + /* count em up */; + + cl_assert_equal_i(GIT_ITEROVER, error); + cl_assert_equal_i(expected_count, count); + + git_iterator_free(i); + git_tree_free(head); +} + +void test_iterator_tree__range_icase(void) +{ + g_repo = cl_git_sandbox_init("testrepo"); + + check_tree_range(g_repo, "B", "C", false, 0); + check_tree_range(g_repo, "B", "C", true, 1); + check_tree_range(g_repo, "b", "c", false, 1); + check_tree_range(g_repo, "b", "c", true, 1); + + check_tree_range(g_repo, "a", "z", false, 3); + check_tree_range(g_repo, "a", "z", true, 4); + check_tree_range(g_repo, "A", "Z", false, 1); + check_tree_range(g_repo, "A", "Z", true, 4); + check_tree_range(g_repo, "a", "Z", false, 0); + check_tree_range(g_repo, "a", "Z", true, 4); + check_tree_range(g_repo, "A", "z", false, 4); + check_tree_range(g_repo, "A", "z", true, 4); + + check_tree_range(g_repo, "new.txt", "new.txt", true, 1); + check_tree_range(g_repo, "new.txt", "new.txt", false, 1); + check_tree_range(g_repo, "README", "README", true, 1); + check_tree_range(g_repo, "README", "README", false, 1); +} + +void test_iterator_tree__icase_0(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_tree *head; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); + + git_tree_free(head); +} + +void test_iterator_tree__icase_1(void) +{ + git_iterator *i; + git_tree *head; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + /* auto expand with no tree entries */ + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + /* auto expand with tree entries */ + i_opts.start = "c"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); + + git_tree_free(head); +} + +void test_iterator_tree__icase_2(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_tree *head; + static const char *expect_basic[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL, + }; + static const char *expect_trees[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/", + "subdir/current_file", + "subdir/deleted_file", + "subdir/modified_file", + NULL, + }; + static const char *expect_noauto[] = { + "current_file", + "file_deleted", + "modified_file", + "staged_changes", + "staged_changes_file_deleted", + "staged_changes_modified_file", + "staged_delete_file_deleted", + "staged_delete_modified_file", + "subdir.txt", + "subdir/", + NULL + }; + + g_repo = cl_git_sandbox_init("status"); + + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_tree(&i, head, NULL)); + expect_iterator_items(i, 12, expect_basic, 12, expect_basic); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 13, expect_trees, 13, expect_trees); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); + expect_iterator_items(i, 10, expect_noauto, 13, expect_trees); + git_iterator_free(i); + + git_tree_free(head); +} + +/* "b=name,t=name", blob_id, tree_id */ +static void build_test_tree( + git_oid *out, git_repository *repo, const char *fmt, ...) +{ + git_oid *id; + git_treebuilder *builder; + const char *scan = fmt, *next; + char type, delimiter; + git_filemode_t mode = GIT_FILEMODE_BLOB; + git_buf name = GIT_BUF_INIT; + va_list arglist; + + cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */ + + va_start(arglist, fmt); + while (*scan) { + switch (type = *scan++) { + case 't': case 'T': mode = GIT_FILEMODE_TREE; break; + case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break; + default: + cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B'); + } + + delimiter = *scan++; /* read and skip delimiter */ + for (next = scan; *next && *next != delimiter; ++next) + /* seek end */; + cl_git_pass(git_buf_set(&name, scan, (size_t)(next - scan))); + for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan) + /* skip delimiter and optional comma */; + + id = va_arg(arglist, git_oid *); + + cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode)); + } + va_end(arglist); + + cl_git_pass(git_treebuilder_write(out, builder)); + + git_treebuilder_free(builder); + git_buf_free(&name); +} + +void test_iterator_tree__case_conflicts_0(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, biga_id, littlea_id, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/1.file", "A/3.file", "a/2.file", "a/4.file" }; + const char *expect_ci[] = { + "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; + const char *expect_cs_trees[] = { + "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" }; + const char *expect_ci_trees[] = { + "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */ + build_test_tree( + &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id); + build_test_tree( + &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id); + build_test_tree( + &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_cs, 4, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_ci, 4, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__case_conflicts_1(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" }; + const char *expect_ci[] = { + "A/a", "a/b", "A/b/1", "A/c" }; + const char *expect_cs_trees[] = { + "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" }; + const char *expect_ci_trees[] = { + "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + /* create: A/a A/b/1 A/c a/a a/b a/C */ + build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id); + build_test_tree( + &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id); + build_test_tree( + &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id); + build_test_tree( + &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_cs, 6, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 4, expect_ci, 4, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__case_conflicts_2(void) +{ + const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; + git_tree *tree; + git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + const char *expect_cs[] = { + "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO", + "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO", + "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO", + "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO", + "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO", + "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO", + "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO", + "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", }; + const char *expect_ci[] = { + "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", + "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", + "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", + "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", + "A/B/C/D/foo", }; + const char *expect_ci_trees[] = { + "A/", "A/B/", "A/B/C/", "A/B/C/D/", + "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", + "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", + "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", + "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", + "A/B/C/D/foo", }; + + g_repo = cl_git_sandbox_init("icase"); + + cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ + + build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2); + + build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id); + build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id); + build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); + build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); + + build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2); + + build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2); + + cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 32, expect_cs, 32, expect_cs); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 17, expect_ci, 17, expect_ci); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees); + git_iterator_free(i); + + git_tree_free(tree); +} + +void test_iterator_tree__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + bool default_icase; + int expect; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + /* In this test we DO NOT force a case on the iterators and verify default behavior. */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "c"; + i_opts.end = NULL; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ + expect = ((default_icase) ? 6 : 4); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + i_opts.start = NULL; + i_opts.end = "e"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + default_icase = git_iterator_ignore_case(i); + /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ + expect = ((default_icase) ? 5 : 6); + expect_iterator_items(i, expect, NULL, expect, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} + +void test_iterator_tree__pathlist_icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + git_repository_head_tree(&tree, g_repo); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 1, NULL, 1, NULL); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, 2, NULL, 2, NULL); + git_iterator_free(i); + + git_vector_free(&filelist); + git_tree_free(tree); +} + +void test_iterator_tree__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + const char *expected[] = { "subdir/README", "subdir/new.txt", + "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 4; + + const char *expected2[] = { "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len2 = 2; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "subdir/subdir2")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len2, expected2, expected_len2, expected2); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + +void test_iterator_tree__pathlist_with_directory_include_tree_nodes(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + + const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", + "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 6; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + +void test_iterator_tree__pathlist_no_match(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_tree *tree; + const git_index_entry *entry; + + g_repo = cl_git_sandbox_init("testrepo2"); + git_repository_head_tree(&tree, g_repo); + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "nonexistent/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); + cl_assert_equal_i(GIT_ITEROVER, git_iterator_current(&entry, i)); + git_iterator_free(i); + + git_tree_free(tree); + git_vector_free(&filelist); +} + diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c new file mode 100644 index 000000000..28fcc0d23 --- /dev/null +++ b/tests/iterator/workdir.c @@ -0,0 +1,1471 @@ +#include "clar_libgit2.h" +#include "iterator.h" +#include "repository.h" +#include "fileops.h" +#include "../submodule/submodule_helpers.h" +#include "iterator_helpers.h" +#include + +static git_repository *g_repo; + +void test_iterator_workdir__initialize(void) +{ +} + +void test_iterator_workdir__cleanup(void) +{ + cl_git_sandbox_cleanup(); + g_repo = NULL; +} + +static void workdir_iterator_test( + const char *sandbox, + const char *start, + const char *end, + int expected_count, + int expected_ignores, + const char **expected_names, + const char *an_ignored_name) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, count = 0, count_all = 0, count_all_post_reset = 0; + + g_repo = cl_git_sandbox_init(sandbox); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + error = git_iterator_current(&entry, i); + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + + while (entry != NULL) { + int ignored = git_iterator_current_is_ignored(i); + + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into(&entry, i)); + continue; + } + + if (expected_names != NULL) + cl_assert_equal_s(expected_names[count_all], entry->path); + + if (an_ignored_name && strcmp(an_ignored_name,entry->path)==0) + cl_assert(ignored); + + if (!ignored) + count++; + count_all++; + + error = git_iterator_advance(&entry, i); + + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + } + + cl_assert_equal_i(expected_count, count); + cl_assert_equal_i(expected_count + expected_ignores, count_all); + + cl_git_pass(git_iterator_reset(i)); + + error = git_iterator_current(&entry, i); + cl_assert((error == 0 && entry != NULL) || + (error == GIT_ITEROVER && entry == NULL)); + + while (entry != NULL) { + if (S_ISDIR(entry->mode)) { + cl_git_pass(git_iterator_advance_into(&entry, i)); + continue; + } + + if (expected_names != NULL) + cl_assert_equal_s( + expected_names[count_all_post_reset], entry->path); + count_all_post_reset++; + + error = git_iterator_advance(&entry, i); + cl_assert(error == 0 || error == GIT_ITEROVER); + } + + cl_assert_equal_i(count_all, count_all_post_reset); + + git_iterator_free(i); +} + +void test_iterator_workdir__0(void) +{ + workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign"); +} + +static const char *status_paths[] = { + "current_file", + "ignored_file", + "modified_file", + "new_file", + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_iterator_workdir__1(void) +{ + workdir_iterator_test( + "status", NULL, NULL, 13, 1, status_paths, "ignored_file"); +} + +static const char *status_paths_range_0[] = { + "staged_changes", + "staged_changes_modified_file", + "staged_delete_modified_file", + "staged_new_file", + "staged_new_file_modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_0(void) +{ + workdir_iterator_test( + "status", "staged", "staged", 5, 0, status_paths_range_0, NULL); +} + +static const char *status_paths_range_1[] = { + "modified_file", NULL +}; + +void test_iterator_workdir__1_ranged_1(void) +{ + workdir_iterator_test( + "status", "modified_file", "modified_file", + 1, 0, status_paths_range_1, NULL); +} + +static const char *status_paths_range_3[] = { + "subdir.txt", + "subdir/current_file", + "subdir/modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_3(void) +{ + workdir_iterator_test( + "status", "subdir", "subdir/modified_file", + 3, 0, status_paths_range_3, NULL); +} + +static const char *status_paths_range_4[] = { + "subdir/current_file", + "subdir/modified_file", + "subdir/new_file", + "\xe8\xbf\x99", + NULL +}; + +void test_iterator_workdir__1_ranged_4(void) +{ + workdir_iterator_test( + "status", "subdir/", NULL, 4, 0, status_paths_range_4, NULL); +} + +static const char *status_paths_range_5[] = { + "subdir/modified_file", + NULL +}; + +void test_iterator_workdir__1_ranged_5(void) +{ + workdir_iterator_test( + "status", "subdir/modified_file", "subdir/modified_file", + 1, 0, status_paths_range_5, NULL); +} + +void test_iterator_workdir__1_ranged_5_1_ranged_empty_0(void) +{ + workdir_iterator_test( + "status", "\xff_does_not_exist", NULL, + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__1_ranged_empty_1(void) +{ + workdir_iterator_test( + "status", "empty", "empty", + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__1_ranged_empty_2(void) +{ + workdir_iterator_test( + "status", NULL, "aaaa_empty_before", + 0, 0, NULL, NULL); +} + +void test_iterator_workdir__builtin_ignores(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int idx; + static struct { + const char *path; + bool ignored; + } expected[] = { + { "dir/", true }, + { "file", false }, + { "ign", true }, + { "macro_bad", false }, + { "macro_test", false }, + { "root_test1", false }, + { "root_test2", false }, + { "root_test3", false }, + { "root_test4.txt", false }, + { "sub/", false }, + { "sub/.gitattributes", false }, + { "sub/abc", false }, + { "sub/dir/", true }, + { "sub/file", false }, + { "sub/ign/", true }, + { "sub/sub/", false }, + { "sub/sub/.gitattributes", false }, + { "sub/sub/dir", false }, /* file is not actually a dir */ + { "sub/sub/file", false }, + { NULL, false } + }; + + g_repo = cl_git_sandbox_init("attr"); + + cl_git_pass(p_mkdir("attr/sub/sub/.git", 0777)); + cl_git_mkfile("attr/sub/.git", "whatever"); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + i_opts.start = "dir"; + i_opts.end = "sub/sub/file"; + + cl_git_pass(git_iterator_for_workdir( + &i, g_repo, NULL, NULL, &i_opts)); + cl_git_pass(git_iterator_current(&entry, i)); + + for (idx = 0; entry != NULL; ++idx) { + int ignored = git_iterator_current_is_ignored(i); + + cl_assert_equal_s(expected[idx].path, entry->path); + cl_assert_(ignored == expected[idx].ignored, expected[idx].path); + + if (!ignored && + (entry->mode == GIT_FILEMODE_TREE || + entry->mode == GIT_FILEMODE_COMMIT)) + { + /* it is possible to advance "into" a submodule */ + cl_git_pass(git_iterator_advance_into(&entry, i)); + } else { + int error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + } + + cl_assert(expected[idx].path == NULL); + + git_iterator_free(i); +} + +static void check_wd_first_through_third_range( + git_repository *repo, const char *start, const char *end) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + const git_index_entry *entry; + int error, idx; + static const char *expected[] = { "FIRST", "second", "THIRD", NULL }; + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_workdir( + &i, repo, NULL, NULL, &i_opts)); + cl_git_pass(git_iterator_current(&entry, i)); + + for (idx = 0; entry != NULL; ++idx) { + cl_assert_equal_s(expected[idx], entry->path); + + error = git_iterator_advance(&entry, i); + cl_assert(!error || error == GIT_ITEROVER); + } + + cl_assert(expected[idx] == NULL); + + git_iterator_free(i); +} + +void test_iterator_workdir__handles_icase_range(void) +{ + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt"); + + cl_git_mkfile("empty_standard_repo/before", "whatever\n"); + cl_git_mkfile("empty_standard_repo/FIRST", "whatever\n"); + cl_git_mkfile("empty_standard_repo/second", "whatever\n"); + cl_git_mkfile("empty_standard_repo/THIRD", "whatever\n"); + cl_git_mkfile("empty_standard_repo/zafter", "whatever\n"); + cl_git_mkfile("empty_standard_repo/Zlast", "whatever\n"); + + check_wd_first_through_third_range(g_repo, "first", "third"); + check_wd_first_through_third_range(g_repo, "FIRST", "THIRD"); + check_wd_first_through_third_range(g_repo, "first", "THIRD"); + check_wd_first_through_third_range(g_repo, "FIRST", "third"); + check_wd_first_through_third_range(g_repo, "FirSt", "tHiRd"); +} + +/* + * The workdir iterator is like the filesystem iterator, but honors + * special git type constructs (ignores, submodules, etc). + */ + +void test_iterator_workdir__icase(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 20, NULL, 20, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 22, NULL, 22, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 12, NULL, 22, NULL); + git_iterator_free(i); +} + +void test_iterator_workdir__icase_starts_and_ends(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 7, NULL, 7, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 3, NULL, 3, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 5, NULL, 8, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 1, NULL, 4, NULL); + git_iterator_free(i); + + /* auto expand with no tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 5, NULL, 5, NULL); + git_iterator_free(i); + + /* auto expand with tree entries */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 14, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 6, NULL, 6, NULL); + git_iterator_free(i); + + /* no auto expand (implies trees included) */ + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 9, NULL, 14, NULL); + git_iterator_free(i); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, 1, NULL, 6, NULL); + git_iterator_free(i); +} + +static void build_workdir_tree(const char *root, int dirs, int subs) +{ + int i, j; + char buf[64], sub[64]; + + for (i = 0; i < dirs; ++i) { + if (i % 2 == 0) { + p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); + + p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i); + cl_git_mkfile(buf, buf); + buf[strlen(buf) - 5] = '\0'; + } else { + p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); + } + + for (j = 0; j < subs; ++j) { + switch (j % 4) { + case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break; + case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break; + case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break; + case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break; + } + cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); + + if (j % 2 == 0) { + size_t sublen = strlen(sub); + memcpy(&sub[sublen], "/file", sizeof("/file")); + cl_git_mkfile(sub, sub); + sub[sublen] = '\0'; + } + } + } +} + +void test_iterator_workdir__depth(void) +{ + git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + build_workdir_tree("icase", 10, 10); + build_workdir_tree("icase/DIR01/sUB01", 50, 0); + build_workdir_tree("icase/dir02/sUB01", 50, 0); + + /* auto expand with no tree entries */ + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + expect_iterator_items(iter, 125, NULL, 125, NULL); + git_iterator_free(iter); + + /* auto expand with tree entries (empty dirs silently skipped) */ + iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); + expect_iterator_items(iter, 337, NULL, 337, NULL); + git_iterator_free(iter); +} + +/* The filesystem iterator is a workdir iterator without any special + * workdir handling capabilities (ignores, submodules, etc). + */ +void test_iterator_workdir__filesystem(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + static const char *expect_base[] = { + "DIR01/Sub02/file", + "DIR01/sub00/file", + "current_file", + "dir00/Sub02/file", + "dir00/file", + "dir00/sub00/file", + "modified_file", + "new_file", + NULL, + }; + static const char *expect_trees[] = { + "DIR01/", + "DIR01/SUB03/", + "DIR01/Sub02/", + "DIR01/Sub02/file", + "DIR01/sUB01/", + "DIR01/sub00/", + "DIR01/sub00/file", + "current_file", + "dir00/", + "dir00/SUB03/", + "dir00/Sub02/", + "dir00/Sub02/file", + "dir00/file", + "dir00/sUB01/", + "dir00/sub00/", + "dir00/sub00/file", + "modified_file", + "new_file", + NULL, + }; + static const char *expect_noauto[] = { + "DIR01/", + "current_file", + "dir00/", + "modified_file", + "new_file", + NULL, + }; + + g_repo = cl_git_sandbox_init("status"); + + build_workdir_tree("status/subdir", 2, 4); + + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL)); + expect_iterator_items(i, 8, expect_base, 8, expect_base); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 18, expect_trees, 18, expect_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); + git_iterator_free(i); + + git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); + git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 8, expect_base, 8, expect_base); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 18, expect_trees, 18, expect_trees); + git_iterator_free(i); + + i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); + expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); + git_iterator_free(i); +} + +void test_iterator_workdir__filesystem2(void) +{ + git_iterator *i; + static const char *expect_base[] = { + "heads/br2", + "heads/dir", + "heads/ident", + "heads/long-file-name", + "heads/master", + "heads/packed-test", + "heads/subtrees", + "heads/test", + "tags/e90810b", + "tags/foo/bar", + "tags/foo/foo/bar", + "tags/point_to_blob", + "tags/test", + NULL, + }; + + g_repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_iterator_for_filesystem( + &i, "testrepo/.git/refs", NULL)); + expect_iterator_items(i, 13, expect_base, 13, expect_base); + git_iterator_free(i); +} + +/* Lots of empty dirs, or nearly empty ones, make the old workdir + * iterator cry. Also, segfault. + */ +void test_iterator_workdir__filesystem_gunk(void) +{ + git_iterator *i; + git_buf parent = GIT_BUF_INIT; + int n; + + if (!cl_is_env_set("GITTEST_INVASIVE_SPEED")) + cl_skip(); + + g_repo = cl_git_sandbox_init("testrepo"); + + for (n = 0; n < 100000; n++) { + git_buf_clear(&parent); + git_buf_printf(&parent, "%s/refs/heads/foo/%d/subdir", + git_repository_path(g_repo), n); + cl_assert(!git_buf_oom(&parent)); + + cl_git_pass(git_futils_mkdir(parent.ptr, 0775, GIT_MKDIR_PATH)); + } + + cl_git_pass(git_iterator_for_filesystem(&i, "testrepo/.git/refs", NULL)); + + /* should only have 13 items, since we're not asking for trees to be + * returned. the goal of this test is simply to not crash. + */ + expect_iterator_items(i, 13, NULL, 13, NULL); + git_iterator_free(i); + git_buf_free(&parent); +} + +void test_iterator_workdir__skips_unreadable_dirs(void) +{ + git_iterator *i; + const git_index_entry *e; + + if (!cl_is_chmod_supported()) + return; + +#ifndef GIT_WIN32 + if (geteuid() == 0) + cl_skip(); +#endif + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); + cl_git_mkfile("empty_standard_repo/r/a", "hello"); + cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); + cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); + cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); + cl_git_mkfile("empty_standard_repo/r/c/foo", "aloha"); + cl_git_mkfile("empty_standard_repo/r/d", "final"); + + cl_git_pass(git_iterator_for_filesystem( + &i, "empty_standard_repo/r", NULL)); + + cl_git_pass(git_iterator_advance(&e, i)); /* a */ + cl_assert_equal_s("a", e->path); + + cl_git_pass(git_iterator_advance(&e, i)); /* c/foo */ + cl_assert_equal_s("c/foo", e->path); + + cl_git_pass(git_iterator_advance(&e, i)); /* d */ + cl_assert_equal_s("d", e->path); + + cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); + + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__skips_fifos_and_special_files(void) +{ +#ifndef GIT_WIN32 + git_iterator *i; + const git_index_entry *e; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777)); + cl_git_mkfile("empty_standard_repo/file", "not me"); + + cl_assert(!mkfifo("empty_standard_repo/fifo", 0777)); + cl_assert(!access("empty_standard_repo/fifo", F_OK)); + + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_git_pass(git_iterator_for_filesystem( + &i, "empty_standard_repo", &i_opts)); + + cl_git_pass(git_iterator_advance(&e, i)); /* .git */ + cl_assert(S_ISDIR(e->mode)); + cl_git_pass(git_iterator_advance(&e, i)); /* dir */ + cl_assert(S_ISDIR(e->mode)); + /* skips fifo */ + cl_git_pass(git_iterator_advance(&e, i)); /* file */ + cl_assert(S_ISREG(e->mode)); + + cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); + + git_iterator_free(i); +#endif +} + +void test_iterator_workdir__pathlist(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 100, NULL)); + cl_git_pass(git_vector_insert(&filelist, "a")); + cl_git_pass(git_vector_insert(&filelist, "B")); + cl_git_pass(git_vector_insert(&filelist, "c")); + cl_git_pass(git_vector_insert(&filelist, "D")); + cl_git_pass(git_vector_insert(&filelist, "e")); + cl_git_pass(git_vector_insert(&filelist, "k.a")); + cl_git_pass(git_vector_insert(&filelist, "k.b")); + cl_git_pass(git_vector_insert(&filelist, "k/1")); + cl_git_pass(git_vector_insert(&filelist, "k/a")); + cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); + cl_git_pass(git_vector_insert(&filelist, "L/1")); + + g_repo = cl_git_sandbox_init("icase"); + + /* Test iterators without returning tree entries (but autoexpanding.) */ + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + /* Case sensitive */ + { + const char *expected[] = { + "B", "D", "L/1", "a", "c", "e", "k/1", "k/a" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Case INsensitive */ + { + const char *expected[] = { + "a", "B", "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 8; + + i_opts.start = NULL; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case sensitive. */ + { + const char *expected[] = { "c", "e", "k/1", "k/a" }; + size_t expected_len = 4; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set a start, but no end. Case INsensitive. */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a", "L/1" }; + size_t expected_len = 6; + + i_opts.start = "c"; + i_opts.end = NULL; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case sensitive. */ + { + const char *expected[] = { "B", "D", "L/1", "a", "c", "e" }; + size_t expected_len = 6; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Set no start, but an end. Case INsensitive. */ + { + const char *expected[] = { "a", "B", "c", "D", "e" }; + size_t expected_len = 5; + + i_opts.start = NULL; + i_opts.end = "e"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "c", "e", "k/1" }; + size_t expected_len = 3; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case sensitive */ + { + const char *expected[] = { "k/1" }; + size_t expected_len = 1; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "c", "D", "e", "k/1", "k/a" }; + size_t expected_len = 5; + + i_opts.start = "c"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Start and an end, case INsensitive */ + { + const char *expected[] = { "k/1", "k/a" }; + size_t expected_len = 2; + + i_opts.start = "k"; + i_opts.end = "k/D"; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +void test_iterator_workdir__pathlist_with_dirs(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + + /* Test that a prefix `k` matches folders, even without trailing slash */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a `k/` matches a folder */ + { + const char *expected[] = { "k/1", "k/B", "k/D", "k/a", "k/c" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "k/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* When the iterator is case sensitive, ensure we can't lookup the + * directory with the wrong case. + */ + { + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + } + + /* Test that case insensitive matching works. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that case insensitive matching works without trailing slash. */ + { + const char *expected[] = { "k/1", "k/a", "k/B", "k/c", "k/D" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "K")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +static void create_paths(const char *root, int depth) +{ + git_buf fullpath = GIT_BUF_INIT; + size_t root_len; + int i; + + cl_git_pass(git_buf_puts(&fullpath, root)); + cl_git_pass(git_buf_putc(&fullpath, '/')); + + root_len = fullpath.size; + + for (i = 0; i < 8; i++) { + bool file = (depth == 0 || (i % 2) == 0); + git_buf_truncate(&fullpath, root_len); + cl_git_pass(git_buf_printf(&fullpath, "item%d", i)); + + if (file) { + cl_git_rewritefile(fullpath.ptr, "This is a file!\n"); + } else { + cl_must_pass(p_mkdir(fullpath.ptr, 0777)); + + if (depth > 0) + create_paths(fullpath.ptr, (depth - 1)); + } + } + + git_buf_free(&fullpath); +} + +void test_iterator_workdir__pathlist_for_deeply_nested_item(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = cl_git_sandbox_init("icase"); + create_paths(git_repository_workdir(g_repo), 3); + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { "item1/item3/item5/item7" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/item7")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(4, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + }; + size_t expected_len = 8; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/item5/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(11, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item1/item3/item0", + "item1/item3/item1/item0", "item1/item3/item1/item1", + "item1/item3/item1/item2", "item1/item3/item1/item3", + "item1/item3/item1/item4", "item1/item3/item1/item5", + "item1/item3/item1/item6", "item1/item3/item1/item7", + "item1/item3/item2", + "item1/item3/item3/item0", "item1/item3/item3/item1", + "item1/item3/item3/item2", "item1/item3/item3/item3", + "item1/item3/item3/item4", "item1/item3/item3/item5", + "item1/item3/item3/item6", "item1/item3/item3/item7", + "item1/item3/item4", + "item1/item3/item5/item0", "item1/item3/item5/item1", + "item1/item3/item5/item2", "item1/item3/item5/item3", + "item1/item3/item5/item4", "item1/item3/item5/item5", + "item1/item3/item5/item6", "item1/item3/item5/item7", + "item1/item3/item6", + "item1/item3/item7/item0", "item1/item3/item7/item1", + "item1/item3/item7/item2", "item1/item3/item7/item3", + "item1/item3/item7/item4", "item1/item3/item7/item5", + "item1/item3/item7/item6", "item1/item3/item7/item7", + }; + size_t expected_len = 36; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item1/item3/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(42, i->stat_calls); + git_iterator_free(i); + } + + /* Ensure that we find the single path we're interested in, and we find + * it efficiently, and don't stat the entire world to get there. + */ + { + const char *expected[] = { + "item0", "item1/item2", "item5/item7/item4", "item6", + "item7/item3/item1/item6" }; + size_t expected_len = 5; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "item7/item3/item1/item6")); + cl_git_pass(git_vector_insert(&filelist, "item6")); + cl_git_pass(git_vector_insert(&filelist, "item5/item7/item4")); + cl_git_pass(git_vector_insert(&filelist, "item1/item2")); + cl_git_pass(git_vector_insert(&filelist, "item0")); + + /* also add some things that don't exist or don't match the right type */ + cl_git_pass(git_vector_insert(&filelist, "item2/")); + cl_git_pass(git_vector_insert(&filelist, "itemN")); + cl_git_pass(git_vector_insert(&filelist, "item1/itemA")); + cl_git_pass(git_vector_insert(&filelist, "item5/item3/item4/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + cl_assert_equal_i(14, i->stat_calls); + git_iterator_free(i); + } + + git_vector_free(&filelist); +} + +void test_iterator_workdir__bounded_submodules(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + git_index *index; + git_tree *head; + + cl_git_pass(git_vector_init(&filelist, 5, NULL)); + + g_repo = setup_fixture_submod2(); + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_repository_head_tree(&head, g_repo)); + + /* Test that a submodule matches */ + { + const char *expected[] = { "sm_changed_head" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "sm_changed_head")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that a submodule still matches when suffixed with a '/' */ + { + const char *expected[] = { "sm_changed_head" }; + size_t expected_len = 1; + + git_vector_clear(&filelist); + cl_git_pass(git_vector_insert(&filelist, "sm_changed_head/")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that start/end work with a submodule */ + { + const char *expected[] = { "sm_changed_head", "sm_changed_index" }; + size_t expected_len = 2; + + i_opts.start = "sm_changed_head"; + i_opts.end = "sm_changed_index"; + i_opts.pathlist.strings = NULL; + i_opts.pathlist.count = 0; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + /* Test that start and end allow '/' suffixes of submodules */ + { + const char *expected[] = { "sm_changed_head", "sm_changed_index" }; + size_t expected_len = 2; + + i_opts.start = "sm_changed_head"; + i_opts.end = "sm_changed_index"; + i_opts.pathlist.strings = NULL; + i_opts.pathlist.count = 0; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, index, head, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + } + + git_vector_free(&filelist); + git_index_free(index); + git_tree_free(head); +} + +void test_iterator_workdir__advance_over(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + + /* create an empty directory */ + cl_must_pass(p_mkdir("icase/empty", 0777)); + + /* create a directory in which all contents are ignored */ + cl_must_pass(p_mkdir("icase/all_ignored", 0777)); + cl_git_rewritefile("icase/all_ignored/one", "This is ignored\n"); + cl_git_rewritefile("icase/all_ignored/two", "This, too, is ignored\n"); + cl_git_rewritefile("icase/all_ignored/.gitignore", ".gitignore\none\ntwo\n"); + + /* create a directory in which not all contents are ignored */ + cl_must_pass(p_mkdir("icase/some_ignored", 0777)); + cl_git_rewritefile("icase/some_ignored/one", "This is ignored\n"); + cl_git_rewritefile("icase/some_ignored/two", "This is not ignored\n"); + cl_git_rewritefile("icase/some_ignored/.gitignore", ".gitignore\none\n"); + + /* create a directory which has some empty children */ + cl_must_pass(p_mkdir("icase/empty_children", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty1", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty2", 0777)); + cl_must_pass(p_mkdir("icase/empty_children/empty3", 0777)); + + /* create a directory which will disappear! */ + cl_must_pass(p_mkdir("icase/missing_directory", 0777)); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + cl_must_pass(p_rmdir("icase/missing_directory")); + + expect_advance_over(i, "B", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "D", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "F", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "H", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "J", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "L/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "a", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "all_ignored/", GIT_ITERATOR_STATUS_IGNORED); + expect_advance_over(i, "c", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "e", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "empty/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "empty_children/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "g", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "i", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "k/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "missing_directory/", GIT_ITERATOR_STATUS_EMPTY); + expect_advance_over(i, "some_ignored/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__advance_over_with_pathlist(void) +{ + git_vector pathlist = GIT_VECTOR_INIT; + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + git_vector_insert(&pathlist, "dirA/subdir1/subdir2/file"); + git_vector_insert(&pathlist, "dirB/subdir1/subdir2"); + git_vector_insert(&pathlist, "dirC/subdir1/nonexistent"); + git_vector_insert(&pathlist, "dirD/subdir1/nonexistent"); + git_vector_insert(&pathlist, "dirD/subdir1/subdir2"); + git_vector_insert(&pathlist, "dirD/nonexistent"); + + i_opts.pathlist.strings = (char **)pathlist.contents; + i_opts.pathlist.count = pathlist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + g_repo = cl_git_sandbox_init("icase"); + + /* Create a directory that has a file that is included in our pathlist */ + cl_must_pass(p_mkdir("icase/dirA", 0777)); + cl_must_pass(p_mkdir("icase/dirA/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirA/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirA/subdir1/subdir2/file", "foo!"); + + /* Create a directory that has a directory that is included in our pathlist */ + cl_must_pass(p_mkdir("icase/dirB", 0777)); + cl_must_pass(p_mkdir("icase/dirB/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirB/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirB/subdir1/subdir2/file", "foo!"); + + /* Create a directory that would contain an entry in our pathlist, but + * that entry does not actually exist. We don't know this until we + * advance_over it. We want to distinguish this from an actually empty + * or ignored directory. + */ + cl_must_pass(p_mkdir("icase/dirC", 0777)); + cl_must_pass(p_mkdir("icase/dirC/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirC/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirC/subdir1/subdir2/file", "foo!"); + + /* Create a directory that has a mix of actual and nonexistent paths */ + cl_must_pass(p_mkdir("icase/dirD", 0777)); + cl_must_pass(p_mkdir("icase/dirD/subdir1", 0777)); + cl_must_pass(p_mkdir("icase/dirD/subdir1/subdir2", 0777)); + cl_git_rewritefile("icase/dirD/subdir1/subdir2/file", "foo!"); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + + expect_advance_over(i, "dirA/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "dirB/", GIT_ITERATOR_STATUS_NORMAL); + expect_advance_over(i, "dirC/", GIT_ITERATOR_STATUS_FILTERED); + expect_advance_over(i, "dirD/", GIT_ITERATOR_STATUS_NORMAL); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); + git_vector_free(&pathlist); +} + +void test_iterator_workdir__advance_into(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + + g_repo = cl_git_sandbox_init("icase"); + + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | + GIT_ITERATOR_DONT_AUTOEXPAND; + + cl_must_pass(p_mkdir("icase/Empty", 0777)); + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_advance_into(i, "B"); + expect_advance_into(i, "D"); + expect_advance_into(i, "Empty/"); + expect_advance_into(i, "F"); + expect_advance_into(i, "H"); + expect_advance_into(i, "J"); + expect_advance_into(i, "L/"); + expect_advance_into(i, "L/1"); + expect_advance_into(i, "L/B"); + expect_advance_into(i, "L/D"); + expect_advance_into(i, "L/a"); + expect_advance_into(i, "L/c"); + expect_advance_into(i, "a"); + expect_advance_into(i, "c"); + expect_advance_into(i, "e"); + expect_advance_into(i, "g"); + expect_advance_into(i, "i"); + expect_advance_into(i, "k/"); + expect_advance_into(i, "k/1"); + expect_advance_into(i, "k/B"); + expect_advance_into(i, "k/D"); + expect_advance_into(i, "k/a"); + expect_advance_into(i, "k/c"); + + cl_git_fail_with(GIT_ITEROVER, git_iterator_advance(NULL, i)); + git_iterator_free(i); +} + +void test_iterator_workdir__pathlist_with_directory(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + const char *expected[] = { "subdir/README", "subdir/new.txt", + "subdir/subdir2/README", "subdir/subdir2/new.txt" }; + size_t expected_len = 4; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + g_repo = cl_git_sandbox_init("testrepo2"); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_free(&filelist); +} + +void test_iterator_workdir__pathlist_with_directory_include_trees(void) +{ + git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; + git_vector filelist; + + const char *expected[] = { "subdir/", "subdir/README", "subdir/new.txt", + "subdir/subdir2/", "subdir/subdir2/README", "subdir/subdir2/new.txt", }; + size_t expected_len = 6; + + cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); + cl_git_pass(git_vector_insert(&filelist, "subdir/")); + + g_repo = cl_git_sandbox_init("testrepo2"); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + i_opts.flags |= GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + + cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); + expect_iterator_items(i, expected_len, expected, expected_len, expected); + git_iterator_free(i); + + git_vector_free(&filelist); +} + diff --git a/tests/merge/driver.c b/tests/merge/driver.c new file mode 100644 index 000000000..c75a00742 --- /dev/null +++ b/tests/merge/driver.c @@ -0,0 +1,388 @@ +#include "clar_libgit2.h" +#include "git2/repository.h" +#include "git2/merge.h" +#include "buffer.h" +#include "merge.h" + +#define TEST_REPO_PATH "merge-resolve" +#define BRANCH_ID "7cb63eed597130ba4abb87b3e544b85021905520" + +#define AUTOMERGEABLE_IDSTR "f2e1550a0c9e53d5811175864a29536642ae3821" + +static git_repository *repo; +static git_index *repo_index; +static git_oid automergeable_id; + +static void test_drivers_register(void); +static void test_drivers_unregister(void); + +void test_merge_driver__initialize(void) +{ + git_config *cfg; + + repo = cl_git_sandbox_init(TEST_REPO_PATH); + git_repository_index(&repo_index, repo); + + git_oid_fromstr(&automergeable_id, AUTOMERGEABLE_IDSTR); + + /* Ensure that the user's merge.conflictstyle doesn't interfere */ + cl_git_pass(git_repository_config(&cfg, repo)); + + cl_git_pass(git_config_set_string(cfg, "merge.conflictstyle", "merge")); + cl_git_pass(git_config_set_bool(cfg, "core.autocrlf", false)); + + test_drivers_register(); + + git_config_free(cfg); +} + +void test_merge_driver__cleanup(void) +{ + test_drivers_unregister(); + + git_index_free(repo_index); + cl_git_sandbox_cleanup(); +} + +struct test_merge_driver { + git_merge_driver base; + int initialized; + int shutdown; +}; + +static int test_driver_init(git_merge_driver *s) +{ + struct test_merge_driver *self = (struct test_merge_driver *)s; + self->initialized = 1; + return 0; +} + +static void test_driver_shutdown(git_merge_driver *s) +{ + struct test_merge_driver *self = (struct test_merge_driver *)s; + self->shutdown = 1; +} + +static int test_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(s); + GIT_UNUSED(src); + + *path_out = "applied.txt"; + *mode_out = GIT_FILEMODE_BLOB; + + return git_buf_printf(merged_out, "This is the `%s` driver.\n", + filter_name); +} + +static struct test_merge_driver test_driver_custom = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + test_driver_apply, + }, + 0, + 0, +}; + +static struct test_merge_driver test_driver_wildcard = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + test_driver_apply, + }, + 0, + 0, +}; + +static void test_drivers_register(void) +{ + cl_git_pass(git_merge_driver_register("custom", &test_driver_custom.base)); + cl_git_pass(git_merge_driver_register("*", &test_driver_wildcard.base)); +} + +static void test_drivers_unregister(void) +{ + cl_git_pass(git_merge_driver_unregister("custom")); + cl_git_pass(git_merge_driver_unregister("*")); +} + +static void set_gitattributes_to(const char *driver) +{ + git_buf line = GIT_BUF_INIT; + + if (driver && strcmp(driver, "")) + git_buf_printf(&line, "automergeable.txt merge=%s\n", driver); + else if (driver) + git_buf_printf(&line, "automergeable.txt merge\n"); + else + git_buf_printf(&line, "automergeable.txt -merge\n"); + + cl_assert(!git_buf_oom(&line)); + + cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", line.ptr); + git_buf_free(&line); +} + +static void merge_branch(void) +{ + git_oid their_id; + git_annotated_commit *their_head; + + cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, + 1, NULL, NULL)); + + git_annotated_commit_free(their_head); +} + +void test_merge_driver__custom(void) +{ + const char *expected = "This is the `custom` driver.\n"; + set_gitattributes_to("custom"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__wildcard(void) +{ + const char *expected = "This is the `foobar` driver.\n"; + set_gitattributes_to("foobar"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__shutdown_is_called(void) +{ + test_driver_custom.initialized = 0; + test_driver_custom.shutdown = 0; + test_driver_wildcard.initialized = 0; + test_driver_wildcard.shutdown = 0; + + /* run the merge with the custom driver */ + set_gitattributes_to("custom"); + merge_branch(); + + /* unregister the drivers, ensure their shutdown function is called */ + test_drivers_unregister(); + + /* since the `custom` driver was used, it should have been initialized and + * shutdown, but the wildcard driver was not used at all and should not + * have been initialized or shutdown. + */ + cl_assert(test_driver_custom.initialized); + cl_assert(test_driver_custom.shutdown); + cl_assert(!test_driver_wildcard.initialized); + cl_assert(!test_driver_wildcard.shutdown); + + test_drivers_register(); +} + +static int defer_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(s); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_PASSTHROUGH; +} + +static struct test_merge_driver test_driver_defer_apply = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + defer_driver_apply, + }, + 0, + 0, +}; + +void test_merge_driver__apply_can_defer(void) +{ + const git_index_entry *idx; + + cl_git_pass(git_merge_driver_register("defer", + &test_driver_defer_apply.base)); + + set_gitattributes_to("defer"); + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + git_merge_driver_unregister("defer"); +} + +static int conflict_driver_apply( + git_merge_driver *s, + const char **path_out, + uint32_t *mode_out, + git_buf *merged_out, + const char *filter_name, + const git_merge_driver_source *src) +{ + GIT_UNUSED(s); + GIT_UNUSED(path_out); + GIT_UNUSED(mode_out); + GIT_UNUSED(merged_out); + GIT_UNUSED(filter_name); + GIT_UNUSED(src); + + return GIT_EMERGECONFLICT; +} + +static struct test_merge_driver test_driver_conflict_apply = { + { + GIT_MERGE_DRIVER_VERSION, + test_driver_init, + test_driver_shutdown, + conflict_driver_apply, + }, + 0, + 0, +}; + +void test_merge_driver__apply_can_conflict(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + cl_git_pass(git_merge_driver_register("conflict", + &test_driver_conflict_apply.base)); + + set_gitattributes_to("conflict"); + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); + + git_merge_driver_unregister("conflict"); +} + +void test_merge_driver__default_can_be_specified(void) +{ + git_oid their_id; + git_annotated_commit *their_head; + git_merge_options merge_opts = GIT_MERGE_OPTIONS_INIT; + const char *expected = "This is the `custom` driver.\n"; + + merge_opts.default_driver = "custom"; + + cl_git_pass(git_oid_fromstr(&their_id, BRANCH_ID)); + cl_git_pass(git_annotated_commit_lookup(&their_head, repo, &their_id)); + + cl_git_pass(git_merge(repo, (const git_annotated_commit **)&their_head, + 1, &merge_opts, NULL)); + + git_annotated_commit_free(their_head); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__honors_builtin_mergedefault(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + cl_repo_set_string(repo, "merge.default", "binary"); + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); +} + +void test_merge_driver__honors_custom_mergedefault(void) +{ + const char *expected = "This is the `custom` driver.\n"; + + cl_repo_set_string(repo, "merge.default", "custom"); + merge_branch(); + + cl_assert_equal_file(expected, strlen(expected), + TEST_REPO_PATH "/applied.txt"); +} + +void test_merge_driver__mergedefault_deferring_falls_back_to_text(void) +{ + const git_index_entry *idx; + + cl_git_pass(git_merge_driver_register("defer", + &test_driver_defer_apply.base)); + + cl_repo_set_string(repo, "merge.default", "defer"); + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + git_merge_driver_unregister("defer"); +} + +void test_merge_driver__set_forces_text(void) +{ + const git_index_entry *idx; + + /* `merge` without specifying a driver indicates `text` */ + set_gitattributes_to(""); + cl_repo_set_string(repo, "merge.default", "custom"); + + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); +} + +void test_merge_driver__unset_forces_binary(void) +{ + const git_index_entry *ancestor, *ours, *theirs; + + /* `-merge` without specifying a driver indicates `binary` */ + set_gitattributes_to(NULL); + cl_repo_set_string(repo, "merge.default", "custom"); + + merge_branch(); + + cl_git_pass(git_index_conflict_get(&ancestor, &ours, &theirs, + repo_index, "automergeable.txt")); +} + +void test_merge_driver__not_configured_driver_falls_back(void) +{ + const git_index_entry *idx; + + test_drivers_unregister(); + + /* `merge` without specifying a driver indicates `text` */ + set_gitattributes_to("notfound"); + + merge_branch(); + + cl_assert((idx = git_index_get_bypath(repo_index, "automergeable.txt", 0))); + cl_assert_equal_oid(&automergeable_id, &idx->id); + + test_drivers_register(); +} + diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c index 3634568de..cd2cf7827 100644 --- a/tests/merge/trees/treediff.c +++ b/tests/merge/trees/treediff.c @@ -3,6 +3,7 @@ #include "merge.h" #include "../merge_helpers.h" #include "diff.h" +#include "diff_tform.h" #include "git2/sys/hashsig.h" static git_repository *repo; diff --git a/tests/merge/workdir/dirty.c b/tests/merge/workdir/dirty.c index 99e33e0cd..a69919f53 100644 --- a/tests/merge/workdir/dirty.c +++ b/tests/merge/workdir/dirty.c @@ -165,8 +165,8 @@ static void hack_index(char *files[]) entry->ctime.seconds = (int32_t)statbuf.st_ctime; entry->mtime.seconds = (int32_t)statbuf.st_mtime; #if defined(GIT_USE_NSEC) - entry->ctime.nanoseconds = statbuf.st_ctim.tv_nsec; - entry->mtime.nanoseconds = statbuf.st_mtim.tv_nsec; + entry->ctime.nanoseconds = statbuf.st_ctime_nsec; + entry->mtime.nanoseconds = statbuf.st_mtime_nsec; #else entry->ctime.nanoseconds = 0; entry->mtime.nanoseconds = 0; diff --git a/tests/merge/workdir/simple.c b/tests/merge/workdir/simple.c index 3cdd15b5a..964532e46 100644 --- a/tests/merge/workdir/simple.c +++ b/tests/merge/workdir/simple.c @@ -330,6 +330,42 @@ void test_merge_workdir_simple__union(void) cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); } +void test_merge_workdir_simple__gitattributes_union(void) +{ + git_buf conflicting_buf = GIT_BUF_INIT; + + struct merge_index_entry merge_index_entries[] = { + ADDED_IN_MASTER_INDEX_ENTRY, + AUTOMERGEABLE_INDEX_ENTRY, + CHANGED_IN_BRANCH_INDEX_ENTRY, + CHANGED_IN_MASTER_INDEX_ENTRY, + + { 0100644, "72cdb057b340205164478565e91eb71647e66891", 0, "conflicting.txt" }, + + UNCHANGED_INDEX_ENTRY, + }; + + struct merge_reuc_entry merge_reuc_entries[] = { + AUTOMERGEABLE_REUC_ENTRY, + CONFLICTING_REUC_ENTRY, + REMOVED_IN_BRANCH_REUC_ENTRY, + REMOVED_IN_MASTER_REUC_ENTRY + }; + + set_core_autocrlf_to(repo, false); + cl_git_mkfile(TEST_REPO_PATH "/.gitattributes", "conflicting.txt merge=union\n"); + + merge_simple_branch(GIT_MERGE_FILE_FAVOR_NORMAL, 0); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, + TEST_REPO_PATH "/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, CONFLICTING_UNION_FILE) == 0); + git_buf_free(&conflicting_buf); + + cl_assert(merge_test_index(repo_index, merge_index_entries, 6)); + cl_assert(merge_test_reuc(repo_index, merge_reuc_entries, 4)); +} + void test_merge_workdir_simple__diff3_from_config(void) { git_config *config; diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c index 5edd79fb8..9ab0d4095 100644 --- a/tests/network/remote/defaultbranch.c +++ b/tests/network/remote/defaultbranch.c @@ -26,7 +26,7 @@ static void assert_default_branch(const char *should) { git_buf name = GIT_BUF_INIT; - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_default_branch(&name, g_remote)); cl_assert_equal_s(should, name.ptr); git_buf_free(&name); @@ -57,7 +57,7 @@ void test_network_remote_defaultbranch__no_default_branch(void) git_buf buf = GIT_BUF_INIT; cl_git_pass(git_remote_create(&remote_b, g_repo_b, "self", git_repository_path(g_repo_b))); - cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_ls(&heads, &len, remote_b)); cl_assert_equal_i(0, len); @@ -80,7 +80,7 @@ void test_network_remote_defaultbranch__detached_sharing_nonbranch_id(void) cl_git_pass(git_reference_create(&ref, g_repo_a, "refs/foo/bar", &id, 1, NULL)); git_reference_free(ref); - cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_fail_with(GIT_ENOTFOUND, git_remote_default_branch(&buf, g_remote)); cl_git_pass(git_clone(&cloned_repo, git_repository_path(g_repo_a), "./local-detached", NULL)); diff --git a/tests/network/remote/local.c b/tests/network/remote/local.c index 4d990ab71..6194802af 100644 --- a/tests/network/remote/local.c +++ b/tests/network/remote/local.c @@ -40,7 +40,7 @@ static void connect_to_local_repository(const char *local_repository) git_buf_sets(&file_path_buf, cl_git_path_url(local_repository)); cl_git_pass(git_remote_create_anonymous(&remote, repo, git_buf_cstr(&file_path_buf))); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); } void test_network_remote_local__connected(void) @@ -214,7 +214,7 @@ void test_network_remote_local__push_to_bare_remote(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localbare.git")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); /* Try to push */ cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); @@ -253,7 +253,7 @@ void test_network_remote_local__push_to_bare_remote_with_file_url(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, url)); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); /* Try to push */ cl_git_pass(git_remote_upload(localremote, &push_array, NULL)); @@ -290,7 +290,7 @@ void test_network_remote_local__push_to_non_bare_remote(void) /* Connect to the bare repo */ cl_git_pass(git_remote_create_anonymous(&localremote, repo, "./localnonbare")); - cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, NULL, NULL, NULL)); /* Try to push */ cl_git_fail_with(GIT_EBAREREPO, git_remote_upload(localremote, &push_array, NULL)); diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c index 46abc6d33..9c7e6b299 100644 --- a/tests/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -93,7 +93,7 @@ void test_network_remote_remotes__error_when_no_push_available(void) cl_git_pass(git_remote_create_anonymous(&r, _repo, cl_fixture("testrepo.git"))); callbacks.transport = git_transport_local; - cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, NULL)); + cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, NULL, NULL)); /* Make sure that push is really not available */ r->transport->push = NULL; @@ -359,7 +359,7 @@ void test_network_remote_remotes__can_load_with_an_empty_url(void) cl_assert(remote->url == NULL); cl_assert(remote->pushurl == NULL); - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_assert(giterr_last() != NULL); cl_assert(giterr_last()->klass == GITERR_INVALID); @@ -376,7 +376,7 @@ void test_network_remote_remotes__can_load_with_only_an_empty_pushurl(void) cl_assert(remote->url == NULL); cl_assert(remote->pushurl == NULL); - cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); git_remote_free(remote); } diff --git a/tests/network/remote/rename.c b/tests/network/remote/rename.c index b44a0ae71..0449575d7 100644 --- a/tests/network/remote/rename.c +++ b/tests/network/remote/rename.c @@ -16,16 +16,6 @@ void test_network_remote_rename__cleanup(void) cl_git_sandbox_cleanup(); } -static int dont_call_me_cb(const char *fetch_refspec, void *payload) -{ - GIT_UNUSED(fetch_refspec); - GIT_UNUSED(payload); - - cl_assert(false); - - return -1; -} - void test_network_remote_rename__renaming_a_remote_moves_related_configuration_section(void) { git_strarray problems = {0}; diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c index b3ac8ae60..4a3096baa 100644 --- a/tests/network/urlparse.c +++ b/tests/network/urlparse.c @@ -121,6 +121,15 @@ void test_network_urlparse__user_pass_port(void) cl_assert_equal_s(pass, "pass"); } +void test_network_urlparse__optional_path(void) +{ + cl_git_fail(gitno_extract_url_parts(&host, &port, &path, &user, &pass, + "https://user:pass@example.com:9191", "8080")); + + cl_git_pass(gitno_extract_url_parts(&host, &port, NULL, &user, &pass, + "https://user:pass@example.com:9191", "8080")); +} + void test_network_urlparse__connection_data_http(void) { cl_git_pass(gitno_connection_data_from_url(&conndata, diff --git a/tests/object/blob/fromchunks.c b/tests/object/blob/fromchunks.c deleted file mode 100644 index b61cabfe1..000000000 --- a/tests/object/blob/fromchunks.c +++ /dev/null @@ -1,156 +0,0 @@ -#include "clar_libgit2.h" -#include "buffer.h" -#include "posix.h" -#include "path.h" -#include "fileops.h" - -static git_repository *repo; -static char textual_content[] = "libgit2\n\r\n\0"; - -void test_object_blob_fromchunks__initialize(void) -{ - repo = cl_git_sandbox_init("testrepo.git"); -} - -void test_object_blob_fromchunks__cleanup(void) -{ - cl_git_sandbox_cleanup(); -} - -static int text_chunked_source_cb(char *content, size_t max_length, void *payload) -{ - int *count; - - GIT_UNUSED(max_length); - - count = (int *)payload; - (*count)--; - - if (*count == 0) - return 0; - - strcpy(content, textual_content); - return (int)strlen(textual_content); -} - -void test_object_blob_fromchunks__can_create_a_blob_from_a_in_memory_chunk_provider(void) -{ - git_oid expected_oid, oid; - git_object *blob; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - cl_git_pass(git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY)); - cl_assert(git_oid_cmp(&expected_oid, git_object_id(blob)) == 0); - - git_object_free(blob); -} - -void test_object_blob_fromchunks__doesnot_overwrite_an_already_existing_object(void) -{ - git_buf path = GIT_BUF_INIT; - git_buf content = GIT_BUF_INIT; - git_oid expected_oid, oid; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - /* Let's replace the content of the blob file storage with something else... */ - cl_git_pass(git_buf_joinpath(&path, git_repository_path(repo), "objects/32/1cbdf08803c744082332332838df6bd160f8f9")); - cl_git_pass(p_unlink(git_buf_cstr(&path))); - cl_git_mkfile(git_buf_cstr(&path), "boom"); - - /* ...request a creation of the same blob... */ - howmany = 7; - cl_git_pass(git_blob_create_fromchunks(&oid, repo, NULL, text_chunked_source_cb, &howmany)); - - /* ...and ensure the content of the faked blob file hasn't been altered */ - cl_git_pass(git_futils_readbuffer(&content, git_buf_cstr(&path))); - cl_assert(!git__strcmp("boom", git_buf_cstr(&content))); - - git_buf_free(&path); - git_buf_free(&content); -} - -#define GITATTR "* text=auto\n" \ - "*.txt text\n" \ - "*.data binary\n" - -static void write_attributes(git_repository *repo) -{ - git_buf buf = GIT_BUF_INIT; - - cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); - cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); - - cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); - cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); - - git_buf_free(&buf); -} - -static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) -{ - git_oid expected_oid, oid; - int howmany = 7; - - cl_git_pass(git_oid_fromstr(&expected_oid, expected_sha)); - - cl_git_pass(git_blob_create_fromchunks(&oid, repo, fake_name, text_chunked_source_cb, &howmany)); - cl_assert(git_oid_cmp(&expected_oid, &oid) == 0); -} - -void test_object_blob_fromchunks__creating_a_blob_from_chunks_honors_the_attributes_directives(void) -{ - write_attributes(repo); - - assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); - assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); -} - -static int failing_chunked_source_cb( - char *content, size_t max_length, void *payload) -{ - int *count = (int *)payload; - - GIT_UNUSED(max_length); - - (*count)--; - if (*count == 0) - return -1234; - - strcpy(content, textual_content); - return (int)strlen(textual_content); -} - -void test_object_blob_fromchunks__can_stop_with_error(void) -{ - git_oid expected_oid, oid; - git_object *blob; - int howmany = 7; - - cl_git_pass(git_oid_fromstr( - &expected_oid, "321cbdf08803c744082332332838df6bd160f8f9")); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); - - cl_git_fail_with(git_blob_create_fromchunks( - &oid, repo, NULL, failing_chunked_source_cb, &howmany), -1234); - - cl_git_fail_with( - git_object_lookup(&blob, repo, &expected_oid, GIT_OBJ_ANY), - GIT_ENOTFOUND); -} - diff --git a/tests/object/blob/fromstream.c b/tests/object/blob/fromstream.c new file mode 100644 index 000000000..d23149bd1 --- /dev/null +++ b/tests/object/blob/fromstream.c @@ -0,0 +1,87 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "posix.h" +#include "path.h" +#include "fileops.h" + +static git_repository *repo; +static char textual_content[] = "libgit2\n\r\n\0"; + +void test_object_blob_fromstream__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_object_blob_fromstream__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_blob_fromstream__multiple_write(void) +{ + git_oid expected_id, id; + git_object *blob; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, "321cbdf08803c744082332332838df6bd160f8f9")); + + cl_git_fail_with(GIT_ENOTFOUND, + git_object_lookup(&blob, repo, &expected_id, GIT_OBJ_ANY)); + + cl_git_pass(git_blob_create_fromstream(&stream, repo, NULL)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + cl_assert_equal_oid(&expected_id, &id); + + cl_git_pass(git_object_lookup(&blob, repo, &expected_id, GIT_OBJ_BLOB)); + + git_object_free(blob); +} + +#define GITATTR "* text=auto\n" \ + "*.txt text\n" \ + "*.data binary\n" + +static void write_attributes(git_repository *repo) +{ + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_buf_joinpath(&buf, git_repository_path(repo), "info")); + cl_git_pass(git_buf_joinpath(&buf, git_buf_cstr(&buf), "attributes")); + + cl_git_pass(git_futils_mkpath2file(git_buf_cstr(&buf), 0777)); + cl_git_rewritefile(git_buf_cstr(&buf), GITATTR); + + git_buf_free(&buf); +} + +static void assert_named_chunked_blob(const char *expected_sha, const char *fake_name) +{ + git_oid expected_id, id; + git_writestream *stream; + int i, howmany = 6; + + cl_git_pass(git_oid_fromstr(&expected_id, expected_sha)); + + cl_git_pass(git_blob_create_fromstream(&stream, repo, fake_name)); + + for (i = 0; i < howmany; i++) + cl_git_pass(stream->write(stream, textual_content, strlen(textual_content))); + + cl_git_pass(git_blob_create_fromstream_commit(&id, stream)); + + cl_assert_equal_oid(&expected_id, &id); +} + +void test_object_blob_fromstream__creating_a_blob_from_chunks_honors_the_attributes_directives(void) +{ + write_attributes(repo); + + assert_named_chunked_blob("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt"); + assert_named_chunked_blob("e9671e138a780833cb689753570fd10a55be84fb", "dummy.dunno"); +} diff --git a/tests/object/tree/update.c b/tests/object/tree/update.c new file mode 100644 index 000000000..b76e8612a --- /dev/null +++ b/tests/object/tree/update.c @@ -0,0 +1,286 @@ +#include "clar_libgit2.h" +#include "tree.h" + +static git_repository *g_repo; + +void test_object_tree_update__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo2"); +} + +void test_object_tree_update__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_object_tree_update__remove_blob(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "README"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__remove_blob_deeper(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "subdir/README"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__remove_all_entries(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path1 = "subdir/subdir2/README"; + const char *path2 = "subdir/subdir2/new.txt"; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path1}, + { GIT_TREE_UPDATE_REMOVE, {{0}}, GIT_FILEMODE_BLOB /* ignored */, path2}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + cl_git_pass(git_index_remove(idx, path1, 0)); + cl_git_pass(git_index_remove(idx, path2, 0)); + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 2, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__replace_blob(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + const char *path = "README"; + git_index_entry entry = { {0} }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, path}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + + entry.path = path; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(idx, &entry)); + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operation via the tree updater */ + cl_git_pass(git_oid_fromstr(&updates[0].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 1, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_blobs(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + git_index_entry entry = { {0} }; + int i; + const char *paths[] = { + "some/deep/path", + "some/other/path", + "a/path/elsewhere", + }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + + for (i = 0; i < 3; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + } + + for (i = 0; i < 2; i++) { + int j; + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + + base_tree = NULL; + if (i == 1) { + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + } + + for (j = 0; j < 3; j++) { + entry.path = paths[j]; + cl_git_pass(git_index_add(idx, &entry)); + } + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operations via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + } + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_blobs_unsorted(void) +{ + git_oid tree_index_id, tree_updater_id, base_id; + git_tree *base_tree; + git_index *idx; + git_index_entry entry = { {0} }; + int i; + const char *paths[] = { + "some/deep/path", + "a/path/elsewhere", + "some/other/path", + }; + + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[0]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[1]}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, paths[2]}, + }; + + cl_git_pass(git_oid_fromstr(&base_id, "c4dc1555e4d4fa0e0c9c3fc46734c7c35b3ce90b")); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_oid_fromstr(&entry.id, "fa49b077972391ad58037050f2a75f74e3671e92")); + + for (i = 0; i < 3; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "fa49b077972391ad58037050f2a75f74e3671e92")); + } + + for (i = 0; i < 2; i++) { + int j; + + /* Create it with an index */ + cl_git_pass(git_index_new(&idx)); + + base_tree = NULL; + if (i == 1) { + cl_git_pass(git_tree_lookup(&base_tree, g_repo, &base_id)); + cl_git_pass(git_index_read_tree(idx, base_tree)); + } + + for (j = 0; j < 3; j++) { + entry.path = paths[j]; + cl_git_pass(git_index_add(idx, &entry)); + } + + cl_git_pass(git_index_write_tree_to(&tree_index_id, idx, g_repo)); + git_index_free(idx); + + /* Perform the same operations via the tree updater */ + cl_git_pass(git_tree_create_updated(&tree_updater_id, g_repo, base_tree, 3, updates)); + + cl_assert_equal_oid(&tree_index_id, &tree_updater_id); + } + + git_tree_free(base_tree); +} + +void test_object_tree_update__add_conflict(void) +{ + int i; + git_oid tree_updater_id; + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir"}, + }; + + for (i = 0; i < 2; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + } + + cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); +} + +void test_object_tree_update__add_conflict2(void) +{ + int i; + git_oid tree_updater_id; + git_tree_update updates[] = { + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_BLOB, "a/dir/blob"}, + { GIT_TREE_UPDATE_UPSERT, {{0}}, GIT_FILEMODE_TREE, "a/dir/blob"}, + }; + + for (i = 0; i < 2; i++) { + cl_git_pass(git_oid_fromstr(&updates[i].id, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")); + } + + cl_git_fail(git_tree_create_updated(&tree_updater_id, g_repo, NULL, 2, updates)); +} diff --git a/tests/odb/foreach.c b/tests/odb/foreach.c index 12b81b4f1..42d706467 100644 --- a/tests/odb/foreach.c +++ b/tests/odb/foreach.c @@ -28,8 +28,8 @@ static int foreach_cb(const git_oid *oid, void *data) /* * $ git --git-dir tests/resources/testrepo.git count-objects --verbose - * count: 47 - * size: 4 + * count: 60 + * size: 240 * in-pack: 1640 * packs: 3 * size-pack: 425 @@ -44,7 +44,7 @@ void test_odb_foreach__foreach(void) git_repository_odb(&_odb, _repo); cl_git_pass(git_odb_foreach(_odb, foreach_cb, &nobj)); - cl_assert_equal_i(47 + 1640, nobj); /* count + in-pack */ + cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ } void test_odb_foreach__one_pack(void) @@ -118,7 +118,7 @@ void test_odb_foreach__files_in_objects_dir(void) cl_git_pass(git_repository_odb(&odb, repo)); cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj)); - cl_assert_equal_i(47 + 1640, nobj); /* count + in-pack */ + cl_assert_equal_i(60 + 1640, nobj); /* count + in-pack */ git_odb_free(odb); git_repository_free(repo); diff --git a/tests/odb/freshen.c b/tests/odb/freshen.c new file mode 100644 index 000000000..d8d6c029a --- /dev/null +++ b/tests/odb/freshen.c @@ -0,0 +1,93 @@ +#include "clar_libgit2.h" +#include "odb.h" +#include "posix.h" + +static git_repository *repo; +static git_odb *odb; + +void test_odb_freshen__initialize(void) +{ + repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_odb(&odb, repo)); +} + +void test_odb_freshen__cleanup(void) +{ + git_odb_free(odb); + cl_git_sandbox_cleanup(); +} + +#define LOOSE_STR "hey\n" +#define LOOSE_ID "1385f264afb75a56a5bec74243be9b367ba4ca08" +#define LOOSE_FN "13/85f264afb75a56a5bec74243be9b367ba4ca08" + +void test_odb_freshen__loose_object(void) +{ + git_oid expected_id, id; + struct stat before, after; + struct p_timeval old_times[2]; + + cl_git_pass(git_oid_fromstr(&expected_id, LOOSE_ID)); + + old_times[0].tv_sec = 1234567890; + old_times[0].tv_usec = 0; + old_times[1].tv_sec = 1234567890; + old_times[1].tv_usec = 0; + + /* set time to way back */ + cl_must_pass(p_utimes("testrepo.git/objects/" LOOSE_FN, old_times)); + cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &before)); + + cl_git_pass(git_odb_write(&id, odb, LOOSE_STR, CONST_STRLEN(LOOSE_STR), + GIT_OBJ_BLOB)); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/" LOOSE_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); +} + +#define PACKED_STR "Testing a readme.txt\n" +#define PACKED_ID "6336846bd5c88d32f93ae57d846683e61ab5c530" +#define PACKED_FN "pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack" + +void test_odb_freshen__packed_object(void) +{ + git_oid expected_id, id; + struct stat before, after; + struct p_timeval old_times[2]; + + cl_git_pass(git_oid_fromstr(&expected_id, PACKED_ID)); + + old_times[0].tv_sec = 1234567890; + old_times[0].tv_usec = 0; + old_times[1].tv_sec = 1234567890; + old_times[1].tv_usec = 0; + + /* set time to way back */ + cl_must_pass(p_utimes("testrepo.git/objects/pack/" PACKED_FN, old_times)); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &before)); + + /* ensure that packfile is freshened */ + cl_git_pass(git_odb_write(&id, odb, PACKED_STR, + CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB)); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); + + cl_assert(before.st_atime < after.st_atime); + cl_assert(before.st_mtime < after.st_mtime); + + memcpy(&before, &after, sizeof(struct stat)); + + /* ensure that the pack file is not freshened again immediately */ + cl_git_pass(git_odb_write(&id, odb, PACKED_STR, + CONST_STRLEN(PACKED_STR), GIT_OBJ_BLOB)); + cl_assert_equal_oid(&expected_id, &id); + cl_must_pass(p_lstat("testrepo.git/objects/pack/" PACKED_FN, &after)); + + cl_assert(before.st_atime == after.st_atime); + cl_assert(before.st_atime_nsec == after.st_atime_nsec); + cl_assert(before.st_mtime == after.st_mtime); + cl_assert(before.st_mtime_nsec == after.st_mtime_nsec); +} + diff --git a/tests/odb/mixed.c b/tests/odb/mixed.c index 2dad4b64e..515eadfde 100644 --- a/tests/odb/mixed.c +++ b/tests/odb/mixed.c @@ -108,3 +108,158 @@ void test_odb_mixed__dup_oid_prefix_0(void) { cl_git_pass(git_odb_read_prefix(&obj, _odb, &oid, strlen(hex))); git_odb_object_free(obj); } + +struct expand_id_test_data { + char *lookup_id; + char *expected_id; + git_otype expected_type; +}; + +struct expand_id_test_data expand_id_test_data[] = { + /* some prefixes and their expected values */ + { "dea509d0", NULL, GIT_OBJ_ANY }, + { "00000000", NULL, GIT_OBJ_ANY }, + { "dea509d0", NULL, GIT_OBJ_ANY }, + { "dea509d09", "dea509d097ce692e167dfc6a48a7a280cc5e877e", GIT_OBJ_BLOB }, + { "dea509d0b", "dea509d0b3cb8ee0650f6ca210bc83f4678851ba", GIT_OBJ_BLOB }, + { "ce0136250", "ce013625030ba8dba906f756967f9e9ca394464a", GIT_OBJ_BLOB }, + { "0ddeaded", NULL, GIT_OBJ_ANY }, + { "4d5979b", "4d5979b468252190cb572ae758aca36928e8a91e", GIT_OBJ_TREE }, + { "0ddeaded", NULL, GIT_OBJ_ANY }, + { "0ddeadede", "0ddeadede9e6d6ccddce0ee1e5749eed0485e5ea", GIT_OBJ_BLOB }, + { "0ddeaded9", "0ddeaded9502971eefe1e41e34d0e536853ae20f", GIT_OBJ_BLOB }, + { "f00b4e", NULL, GIT_OBJ_ANY }, + + /* this OID is too short and should be ambiguous! */ + { "f00", NULL, GIT_OBJ_ANY }, + + /* some full-length object ids */ + { "0000000000000000000000000000000000000000", NULL, GIT_OBJ_ANY }, + { + "dea509d097ce692e167dfc6a48a7a280cc5e877e", + "dea509d097ce692e167dfc6a48a7a280cc5e877e", + GIT_OBJ_BLOB + }, + { "f00f00f00f00f00f00f00f00f00f00f00f00f00f", NULL, GIT_OBJ_ANY }, + { + "4d5979b468252190cb572ae758aca36928e8a91e", + "4d5979b468252190cb572ae758aca36928e8a91e", + GIT_OBJ_TREE + }, + + /* + * ensure we're not leaking the return error code for the + * last lookup if the last object is invalid + */ + { "0ddeadedfff", NULL, GIT_OBJ_ANY }, +}; + +static void setup_prefix_query( + git_odb_expand_id **out_ids, + size_t *out_num) +{ + git_odb_expand_id *ids; + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + cl_assert((ids = git__calloc(num, sizeof(git_odb_expand_id)))); + + for (i = 0; i < num; i++) { + git_odb_expand_id *id = &ids[i]; + + size_t len = strlen(expand_id_test_data[i].lookup_id); + + git_oid_fromstrn(&id->id, expand_id_test_data[i].lookup_id, len); + id->length = (unsigned short)len; + id->type = expand_id_test_data[i].expected_type; + } + + *out_ids = ids; + *out_num = num; +} + +static void assert_found_objects(git_odb_expand_id *ids) +{ + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + for (i = 0; i < num; i++) { + git_oid expected_id = {{0}}; + size_t expected_len = 0; + git_otype expected_type = 0; + + if (expand_id_test_data[i].expected_id) { + git_oid_fromstr(&expected_id, expand_id_test_data[i].expected_id); + expected_len = GIT_OID_HEXSZ; + expected_type = expand_id_test_data[i].expected_type; + } + + cl_assert_equal_oid(&expected_id, &ids[i].id); + cl_assert_equal_i(expected_len, ids[i].length); + cl_assert_equal_i(expected_type, ids[i].type); + } +} + +static void assert_notfound_objects(git_odb_expand_id *ids) +{ + git_oid expected_id = {{0}}; + size_t num, i; + + num = ARRAY_SIZE(expand_id_test_data); + + for (i = 0; i < num; i++) { + cl_assert_equal_oid(&expected_id, &ids[i].id); + cl_assert_equal_i(0, ids[i].length); + cl_assert_equal_i(0, ids[i].type); + } +} + +void test_odb_mixed__expand_ids(void) +{ + git_odb_expand_id *ids; + size_t i, num; + + /* test looking for the actual (correct) types */ + + setup_prefix_query(&ids, &num); + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for an explicit `type == 0` */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = 0; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for an explicit GIT_OBJ_ANY */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = GIT_OBJ_ANY; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_found_objects(ids); + git__free(ids); + + /* test looking for the completely wrong type */ + + setup_prefix_query(&ids, &num); + + for (i = 0; i < num; i++) + ids[i].type = (ids[i].type == GIT_OBJ_BLOB) ? + GIT_OBJ_TREE : GIT_OBJ_BLOB; + + cl_git_pass(git_odb_expand_ids(_odb, ids, num)); + assert_notfound_objects(ids); + git__free(ids); +} + diff --git a/tests/online/badssl.c b/tests/online/badssl.c index 66b090df4..aa4c24d9c 100644 --- a/tests/online/badssl.c +++ b/tests/online/badssl.c @@ -10,37 +10,71 @@ static bool g_has_ssl = true; static bool g_has_ssl = false; #endif +static int cert_check_assert_invalid(git_cert *cert, int valid, const char* host, void *payload) +{ + GIT_UNUSED(cert); GIT_UNUSED(host); GIT_UNUSED(payload); + + cl_assert_equal_i(0, valid); + + return GIT_ECERTIFICATE; +} + void test_online_badssl__expired(void) { + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + if (!g_has_ssl) cl_skip(); cl_git_fail_with(GIT_ECERTIFICATE, git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); + + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", &opts)); } void test_online_badssl__wrong_host(void) { + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + if (!g_has_ssl) cl_skip(); cl_git_fail_with(GIT_ECERTIFICATE, git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &opts)); } void test_online_badssl__self_signed(void) { + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + if (!g_has_ssl) cl_skip(); cl_git_fail_with(GIT_ECERTIFICATE, git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", &opts)); } void test_online_badssl__old_cipher(void) { + git_clone_options opts = GIT_CLONE_OPTIONS_INIT; + opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid; + + /* FIXME: we don't actually reject RC4 anywhere, figure out what to tweak */ + cl_skip(); + if (!g_has_ssl) cl_skip(); - cl_git_fail(git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL)); + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", &opts)); } diff --git a/tests/online/clone.c b/tests/online/clone.c index b84be405c..04fd22d45 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -24,6 +24,9 @@ static char *_remote_ssh_pubkey = NULL; static char *_remote_ssh_privkey = NULL; static char *_remote_ssh_passphrase = NULL; static char *_remote_ssh_fingerprint = NULL; +static char *_remote_proxy_url = NULL; +static char *_remote_proxy_user = NULL; +static char *_remote_proxy_pass = NULL; void test_online_clone__initialize(void) @@ -46,6 +49,9 @@ void test_online_clone__initialize(void) _remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY"); _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE"); _remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT"); + _remote_proxy_url = cl_getenv("GITTEST_REMOTE_PROXY_URL"); + _remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER"); + _remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS"); } void test_online_clone__cleanup(void) @@ -63,6 +69,9 @@ void test_online_clone__cleanup(void) git__free(_remote_ssh_privkey); git__free(_remote_ssh_passphrase); git__free(_remote_ssh_fingerprint); + git__free(_remote_proxy_url); + git__free(_remote_proxy_user); + git__free(_remote_proxy_pass); } void test_online_clone__network_full(void) @@ -653,3 +662,40 @@ void test_online_clone__start_with_http(void) cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); } + +static int called_proxy_creds; +static int proxy_creds(git_cred **out, const char *url, const char *username, unsigned int allowed, void *payload) +{ + GIT_UNUSED(url); + GIT_UNUSED(username); + GIT_UNUSED(allowed); + GIT_UNUSED(payload); + + called_proxy_creds = 1; + return git_cred_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass); +} + +void test_online_clone__proxy_credentials_request(void) +{ + if (!_remote_proxy_url || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + g_options.fetch_opts.proxy_opts.credentials = proxy_creds; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds); +} + +void test_online_clone__proxy_credentials_in_url(void) +{ + if (!_remote_proxy_url) + cl_skip(); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = _remote_proxy_url; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 0); +} diff --git a/tests/online/fetch.c b/tests/online/fetch.c index c12df069f..827cb23c1 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -81,11 +81,11 @@ void test_online_fetch__fetch_twice(void) { git_remote *remote; cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL); + git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); @@ -117,7 +117,7 @@ void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date cl_git_pass(git_repository_open(&_repository, "./fetch/lg2")); cl_git_pass(git_remote_lookup(&remote, _repository, "origin")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_assert_equal_i(false, invoked); @@ -155,7 +155,7 @@ void test_online_fetch__can_cancel(void) options.callbacks.transfer_progress = cancel_at_half; options.callbacks.payload = &bytes_received; - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_fail_with(git_remote_download(remote, NULL, &options), -4321); git_remote_disconnect(remote); git_remote_free(remote); @@ -169,7 +169,7 @@ void test_online_fetch__ls_disconnected(void) cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote)); git_remote_disconnect(remote); cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote)); @@ -187,7 +187,7 @@ void test_online_fetch__remote_symrefs(void) cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git")); - cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL)); git_remote_disconnect(remote); cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c index 9aaad253c..c1ac06dbe 100644 --- a/tests/online/fetchhead.c +++ b/tests/online/fetchhead.c @@ -126,6 +126,8 @@ void test_online_fetchhead__explicit_dst_refspec_creates_branch(void) cl_git_pass(git_branch_lookup(&ref, g_repo, "explicit-refspec", GIT_BRANCH_ALL)); cl_assert_equal_i(refs + 1, count_references()); + + git_reference_free(ref); } void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void) diff --git a/tests/online/push.c b/tests/online/push.c index 77c437622..f72b4f8cb 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -372,7 +372,7 @@ void test_online_push__initialize(void) record_callbacks_data_clear(&_record_cbs_data); - cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL)); + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL)); /* Clean up previously pushed branches. Fails if receive.denyDeletes is * set on the remote. Also, on Git 1.7.0 and newer, you must run diff --git a/tests/patch/parse.c b/tests/patch/parse.c new file mode 100644 index 000000000..8350ac2dd --- /dev/null +++ b/tests/patch/parse.c @@ -0,0 +1,104 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" + +#include "patch_common.h" + +static void ensure_patch_validity(git_patch *patch) +{ + const git_diff_delta *delta; + char idstr[GIT_OID_HEXSZ+1] = {0}; + + cl_assert((delta = git_patch_get_delta(patch)) != NULL); + cl_assert_equal_i(2, delta->nfiles); + + cl_assert_equal_s(delta->old_file.path, "file.txt"); + cl_assert(delta->old_file.mode == GIT_FILEMODE_BLOB); + cl_assert_equal_i(7, delta->old_file.id_abbrev); + git_oid_nfmt(idstr, delta->old_file.id_abbrev, &delta->old_file.id); + cl_assert_equal_s(idstr, "9432026"); + cl_assert_equal_i(0, delta->old_file.size); + + cl_assert_equal_s(delta->new_file.path, "file.txt"); + cl_assert(delta->new_file.mode == GIT_FILEMODE_BLOB); + cl_assert_equal_i(7, delta->new_file.id_abbrev); + git_oid_nfmt(idstr, delta->new_file.id_abbrev, &delta->new_file.id); + cl_assert_equal_s(idstr, "cd8fd12"); + cl_assert_equal_i(0, delta->new_file.size); +} + +void test_patch_parse__original_to_change_middle(void) +{ + git_patch *patch; + + cl_git_pass(git_patch_from_buffer( + &patch, PATCH_ORIGINAL_TO_CHANGE_MIDDLE, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE), NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); +} + +void test_patch_parse__leading_and_trailing_garbage(void) +{ + git_patch *patch; + const char *leading = "This is some leading garbage.\n" + "Maybe it's email headers?\n" + "\n" + PATCH_ORIGINAL_TO_CHANGE_MIDDLE; + const char *trailing = PATCH_ORIGINAL_TO_CHANGE_MIDDLE + "\n" + "This is some trailing garbage.\n" + "Maybe it's an email signature?\n"; + const char *both = "Here's some leading garbage\n" + PATCH_ORIGINAL_TO_CHANGE_MIDDLE + "And here's some trailing.\n"; + + cl_git_pass(git_patch_from_buffer(&patch, leading, strlen(leading), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); + + cl_git_pass(git_patch_from_buffer(&patch, trailing, strlen(trailing), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); + + cl_git_pass(git_patch_from_buffer(&patch, both, strlen(both), + NULL)); + ensure_patch_validity(patch); + git_patch_free(patch); +} + +void test_patch_parse__nonpatches_fail_with_notfound(void) +{ + git_patch *patch; + + cl_git_fail_with(GIT_ENOTFOUND, + git_patch_from_buffer(&patch, PATCH_NOT_A_PATCH, + strlen(PATCH_NOT_A_PATCH), NULL)); +} + +void test_patch_parse__invalid_patches_fails(void) +{ + git_patch *patch; + + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, PATCH_CORRUPT_GIT_HEADER, + strlen(PATCH_CORRUPT_GIT_HEADER), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_NEW_FILE, + strlen(PATCH_CORRUPT_MISSING_NEW_FILE), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_OLD_FILE, + strlen(PATCH_CORRUPT_MISSING_OLD_FILE), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, PATCH_CORRUPT_NO_CHANGES, + strlen(PATCH_CORRUPT_NO_CHANGES), NULL)); + cl_git_fail_with(GIT_ERROR, + git_patch_from_buffer(&patch, + PATCH_CORRUPT_MISSING_HUNK_HEADER, + strlen(PATCH_CORRUPT_MISSING_HUNK_HEADER), NULL)); +} + diff --git a/tests/patch/patch_common.h b/tests/patch/patch_common.h new file mode 100644 index 000000000..6ec554690 --- /dev/null +++ b/tests/patch/patch_common.h @@ -0,0 +1,668 @@ +/* The original file contents */ + +#define FILE_ORIGINAL \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +/* A change in the middle of the file (and the resultant patch) */ + +#define FILE_CHANGE_MIDDLE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(THIS line is changed!)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +/* A change of the first line (and the resultant patch) */ + +#define FILE_CHANGE_FIRSTLINE \ + "hey, change in head!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..c81df1d 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-hey!\n" \ + "+hey, change in head!\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" + +/* A change of the last line (and the resultant patch) */ + +#define FILE_CHANGE_LASTLINE \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "change to the last line.\n" + +#define PATCH_ORIGINAL_TO_CHANGE_LASTLINE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f70db1c 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6,4 +6,4 @@ yes it is!\n" \ + " (this line is changed)\n" \ + " and this\n" \ + " is additional context\n" \ + "-below it!\n" \ + "+change to the last line.\n" + +/* A change of the middle where we remove many lines */ + +#define FILE_CHANGE_MIDDLE_SHRINK \ + "hey!\n" \ + "i've changed a lot, but left the line\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_SHRINK \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..629cd35 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +1,3 @@\n" \ + " hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "+i've changed a lot, but left the line\n" \ + " below it!\n" + +#define PATCH_ORIGINAL_TO_MIDDLE_SHRINK_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..629cd35 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -2,7 +2 @@ hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "+i've changed a lot, but left the line\n" + +/* A change to the middle where we grow many lines */ + +#define FILE_CHANGE_MIDDLE_GROW \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "this line is changed\n" \ + "and this line is added\n" \ + "so is this\n" \ + "(this too)\n" \ + "whee...\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_CHANGE_MIDDLE_GROW \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..207ebca 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,11 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+this line is changed\n" \ + "+and this line is added\n" \ + "+so is this\n" \ + "+(this too)\n" \ + "+whee...\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + + +#define PATCH_ORIGINAL_TO_MIDDLE_GROW_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..207ebca 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6,5 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+this line is changed\n" \ + "+and this line is added\n" \ + "+so is this\n" \ + "+(this too)\n" \ + "+whee...\n" + +/* An insertion at the beginning of the file (and the resultant patch) */ + +#define FILE_PREPEND \ + "insert at front\n" \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" + +#define PATCH_ORIGINAL_TO_PREPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,3 +1,4 @@\n" \ + "+insert at front\n" \ + " hey!\n" \ + " this is some context!\n" \ + " around some lines\n" + +#define PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+insert at front\n" + +/* An insertion at the end of the file (and the resultant patch) */ + +#define FILE_APPEND \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" \ + "insert at end\n" + +#define PATCH_ORIGINAL_TO_APPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..72788bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -7,3 +7,4 @@ yes it is!\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" \ + "+insert at end\n" + +#define PATCH_ORIGINAL_TO_APPEND_NOCONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..72788bb 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -9,0 +10 @@ below it!\n" \ + "+insert at end\n" + +/* An insertion at the beginning and end of file (and the resultant patch) */ + +#define FILE_PREPEND_AND_APPEND \ + "first and\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "last lines\n" + +#define PATCH_ORIGINAL_TO_PREPEND_AND_APPEND \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..f282430 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,4 +1,4 @@\n" \ + "-hey!\n" \ + "+first and\n" \ + " this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + "@@ -6,4 +6,4 @@ yes it is!\n" \ + " (this line is changed)\n" \ + " and this\n" \ + " is additional context\n" \ + "-below it!\n" \ + "+last lines\n" + +#define PATCH_ORIGINAL_TO_EMPTY_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..e69de29 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -1,9 +0,0 @@\n" \ + "-hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "-below it!\n" + +#define PATCH_EMPTY_FILE_TO_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "index e69de29..9432026 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1,9 @@\n" \ + "+hey!\n" \ + "+this is some context!\n" \ + "+around some lines\n" \ + "+that will change\n" \ + "+yes it is!\n" \ + "+(this line is changed)\n" \ + "+and this\n" \ + "+is additional context\n" \ + "+below it!\n" + +#define PATCH_ADD_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "new file mode 100644\n" \ + "index 0000000..9432026\n" \ + "--- /dev/null\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1,9 @@\n" \ + "+hey!\n" \ + "+this is some context!\n" \ + "+around some lines\n" \ + "+that will change\n" \ + "+yes it is!\n" \ + "+(this line is changed)\n" \ + "+and this\n" \ + "+is additional context\n" \ + "+below it!\n" + +#define PATCH_DELETE_ORIGINAL \ + "diff --git a/file.txt b/file.txt\n" \ + "deleted file mode 100644\n" \ + "index 9432026..0000000\n" \ + "--- a/file.txt\n" \ + "+++ /dev/null\n" \ + "@@ -1,9 +0,0 @@\n" \ + "-hey!\n" \ + "-this is some context!\n" \ + "-around some lines\n" \ + "-that will change\n" \ + "-yes it is!\n" \ + "-(this line is changed)\n" \ + "-and this\n" \ + "-is additional context\n" \ + "-below it!\n" + +#define PATCH_RENAME_EXACT \ + "diff --git a/file.txt b/newfile.txt\n" \ + "similarity index 100%\n" \ + "rename from file.txt\n" \ + "rename to newfile.txt\n" + +#define PATCH_RENAME_SIMILAR \ + "diff --git a/file.txt b/newfile.txt\n" \ + "similarity index 77%\n" \ + "rename from file.txt\n" \ + "rename to newfile.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/newfile.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_RENAME_EXACT_QUOTEDNAME \ + "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ + "similarity index 100%\n" \ + "rename from file.txt\n" \ + "rename to \"foo\\\"bar.txt\"\n" + +#define PATCH_RENAME_SIMILAR_QUOTEDNAME \ + "diff --git a/file.txt \"b/foo\\\"bar.txt\"\n" \ + "similarity index 77%\n" \ + "rename from file.txt\n" \ + "rename to \"foo\\\"bar.txt\"\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ \"b/foo\\\"bar.txt\"\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_MODECHANGE_UNCHANGED \ + "diff --git a/file.txt b/file.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" + +#define PATCH_MODECHANGE_MODIFIED \ + "diff --git a/file.txt b/file.txt\n" \ + "old mode 100644\n" \ + "new mode 100755\n" \ + "index 9432026..cd8fd12\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_NOISY \ + "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ + "but actually isn't and should parse ok\n" \ + PATCH_ORIGINAL_TO_CHANGE_MIDDLE \ + "plus some trailing garbage for good measure\n" + +#define PATCH_NOISY_NOCONTEXT \ + "This is some\nleading noise\n@@ - that\nlooks like a hunk header\n" \ + "but actually isn't and should parse ok\n" \ + PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT \ + "plus some trailing garbage for good measure\n" + +#define PATCH_TRUNCATED_1 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" + +#define PATCH_TRUNCATED_2 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define PATCH_TRUNCATED_3 \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -3,7 +3,7 @@ this is some context!\n" \ + " around some lines\n" \ + " that will change\n" \ + " yes it is!\n" \ + "+(THIS line is changed!)\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" + +#define FILE_EMPTY_CONTEXT_ORIGINAL \ + "this\nhas\nan\n\nempty\ncontext\nline\n" + +#define FILE_EMPTY_CONTEXT_MODIFIED \ + "this\nhas\nan\n\nempty...\ncontext\nline\n" + +#define PATCH_EMPTY_CONTEXT \ + "diff --git a/file.txt b/file.txt\n" \ + "index 398d2df..bb15234 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -2,6 +2,6 @@ this\n" \ + " has\n" \ + " an\n" \ + "\n" \ + "-empty\n" \ + "+empty...\n" \ + " context\n" \ + " line\n" + +#define FILE_APPEND_NO_NL \ + "hey!\n" \ + "this is some context!\n" \ + "around some lines\n" \ + "that will change\n" \ + "yes it is!\n" \ + "(this line is changed)\n" \ + "and this\n" \ + "is additional context\n" \ + "below it!\n" \ + "added line with no nl" + +#define PATCH_APPEND_NO_NL \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..83759c0 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -7,3 +7,4 @@ yes it is!\n" \ + " and this\n" \ + " is additional context\n" \ + " below it!\n" \ + "+added line with no nl\n" \ + "\\ No newline at end of file\n" + +#define PATCH_CORRUPT_GIT_HEADER \ + "diff --git a/file.txt\n" \ + "index 9432026..0f39b9a 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +1 @@\n" \ + "+insert at front\n" + +#define PATCH_CORRUPT_MISSING_NEW_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_CORRUPT_MISSING_OLD_FILE \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "+++ b/file.txt\n" \ + "@@ -6 +6 @@ yes it is!\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_CORRUPT_NO_CHANGES \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "@@ -0,0 +0,0 @@ yes it is!\n" + +#define PATCH_CORRUPT_MISSING_HUNK_HEADER \ + "diff --git a/file.txt b/file.txt\n" \ + "index 9432026..cd8fd12 100644\n" \ + "--- a/file.txt\n" \ + "+++ b/file.txt\n" \ + "-(this line is changed)\n" \ + "+(THIS line is changed!)\n" + +#define PATCH_NOT_A_PATCH \ + "+++this is not\n" \ + "--actually even\n" \ + " a legitimate \n" \ + "+patch file\n" \ + "-it's something else\n" \ + " entirely!" + +/* binary contents */ + +#define FILE_BINARY_LITERAL_ORIGINAL "\x00\x00\x0a" +#define FILE_BINARY_LITERAL_ORIGINAL_LEN 3 + +#define FILE_BINARY_LITERAL_MODIFIED "\x00\x00\x01\x02\x0a" +#define FILE_BINARY_LITERAL_MODIFIED_LEN 5 + +#define PATCH_BINARY_LITERAL \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index bd474b2519cc15eab801ff851cc7d50f0dee49a1..9ac35ff15cd8864aeafd889e4826a3150f0b06c4 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "literal 3\n" \ + "Kc${Nk-~s>u4FC%O\n\n" + +#define FILE_BINARY_DELTA_ORIGINAL \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x54\x68\x69" \ + "\x73\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x69\x74\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x6f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_ORIGINAL_LEN 209 + +#define FILE_BINARY_DELTA_MODIFIED \ + "\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02\x0a\x5a\x5a\x5a" \ + "\x5a\x20\x69\x73\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x66\x69" \ + "\x6c\x65\x2c\x20\x62\x79\x20\x76\x69\x72\x74\x75\x65\x20\x6f\x66" \ + "\x20\x68\x61\x76\x69\x6e\x67\x20\x73\x6f\x6d\x65\x20\x6e\x75\x6c" \ + "\x6c\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a\x57\x65\x27\x72\x65\x20\x67\x6f\x69\x6e\x67\x20\x74\x6f\x20" \ + "\x63\x68\x61\x6e\x67\x65\x20\x70\x6f\x72\x74\x69\x6f\x6e\x73\x20" \ + "\x6f\x66\x20\x49\x54\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00" \ + "\x00\x01\x02\x0a\x53\x4f\x20\x74\x68\x61\x74\x20\x77\x65\x20\x67" \ + "\x69\x74\x20\x61\x20\x62\x69\x6e\x61\x72\x79\x20\x64\x65\x6c\x74" \ + "\x61\x20\x69\x6e\x73\x74\x65\x61\x64\x20\x6f\x66\x20\x74\x68\x65" \ + "\x20\x64\x65\x66\x6c\x61\x74\x65\x64\x20\x63\x6f\x6e\x74\x65\x6e" \ + "\x74\x73\x2e\x0a\x00\x00\x01\x02\x00\x00\x01\x02\x00\x00\x01\x02" \ + "\x0a" +#define FILE_BINARY_DELTA_MODIFIED_LEN 209 + +#define PATCH_BINARY_DELTA \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "delta 48\n" \ + "kc$~Y)c#%<%fq{_;hPk4EV4`4>uxE%K7m7r%|HL+L0In7XGynhq\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" + +#define PATCH_BINARY_ADD \ + "diff --git a/binary.bin b/binary.bin\n" \ + "new file mode 100644\n" \ + "index 0000000000000000000000000000000000000000..7c94f9e60bf366033d98e0d551ae37d30faef74a\n" \ + "GIT binary patch\n" \ + "literal 209\n" \ + "zc${60u?oUK5JXSQe8qG&;(u6KCC_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n" \ + "\n" \ + "literal 0\n" \ + "Hc$@C_\n" \ + "zAhe=XX7rNzh<3&##YcwqNHmEKsP<&&m~%Zf;eX@Khr$?aExDmfqyyt+#l^I)3+LMg\n" \ + "kxnAIj9Pfn_|Gh`fP7tlm6j#y{FJYg_IifRlR^R@A08f862mk;8\n\n" + +/* contains an old side that does not match the expected source */ +#define PATCH_BINARY_NOT_REVERSIBLE \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9883b12c4c9c54b4a31137603586169f51..7c94f9e60bf366033d98e0d551ae37d30faef74a 100644\n" \ + "GIT binary patch\n" \ + "literal 5\n" \ + "Mc${NkU}WL~000&M4gdfE\n" \ + "\n" \ + "delta 48\n" \ + "mc$~Y)c#%<%fq{_;hPgsAGK(h)CJASj=y9P)1m{m|^9BI99|yz$\n\n" + +#define PATCH_BINARY_NOT_PRINTED \ + "diff --git a/binary.bin b/binary.bin\n" \ + "index 27184d9..7c94f9e 100644\n" \ + "Binary files a/binary.bin and b/binary.bin differ\n" diff --git a/tests/patch/print.c b/tests/patch/print.c new file mode 100644 index 000000000..62e50b93e --- /dev/null +++ b/tests/patch/print.c @@ -0,0 +1,174 @@ +#include "clar_libgit2.h" +#include "patch.h" +#include "patch_parse.h" + +#include "patch_common.h" + + +/* sanity check the round-trip of patch parsing: ensure that we can parse + * and then print a variety of patch files. + */ + +void patch_print_from_patchfile(const char *data, size_t len) +{ + git_patch *patch; + git_buf buf = GIT_BUF_INIT; + + cl_git_pass(git_patch_from_buffer(&patch, data, len, NULL)); + cl_git_pass(git_patch_to_buf(&buf, patch)); + + cl_assert_equal_s(data, buf.ptr); + + git_patch_free(patch); + git_buf_free(&buf); +} + +void test_patch_print__change_middle(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE)); +} + +void test_patch_print__change_middle_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_CHANGE_MIDDLE_NOCONTEXT)); +} + +void test_patch_print__change_firstline(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE, + strlen(PATCH_ORIGINAL_TO_CHANGE_FIRSTLINE)); +} + +void test_patch_print__change_lastline(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_CHANGE_LASTLINE, + strlen(PATCH_ORIGINAL_TO_CHANGE_LASTLINE)); +} + +void test_patch_print__prepend(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND, + strlen(PATCH_ORIGINAL_TO_PREPEND)); +} + +void test_patch_print__prepend_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_PREPEND_NOCONTEXT)); +} + +void test_patch_print__append(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND, + strlen(PATCH_ORIGINAL_TO_APPEND)); +} + +void test_patch_print__append_nocontext(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT, + strlen(PATCH_ORIGINAL_TO_APPEND_NOCONTEXT)); +} + +void test_patch_print__prepend_and_append(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND, + strlen(PATCH_ORIGINAL_TO_PREPEND_AND_APPEND)); +} + +void test_patch_print__to_empty_file(void) +{ + patch_print_from_patchfile(PATCH_ORIGINAL_TO_EMPTY_FILE, + strlen(PATCH_ORIGINAL_TO_EMPTY_FILE)); +} + +void test_patch_print__from_empty_file(void) +{ + patch_print_from_patchfile(PATCH_EMPTY_FILE_TO_ORIGINAL, + strlen(PATCH_EMPTY_FILE_TO_ORIGINAL)); +} + +void test_patch_print__add(void) +{ + patch_print_from_patchfile(PATCH_ADD_ORIGINAL, + strlen(PATCH_ADD_ORIGINAL)); +} + +void test_patch_print__delete(void) +{ + patch_print_from_patchfile(PATCH_DELETE_ORIGINAL, + strlen(PATCH_DELETE_ORIGINAL)); +} + +void test_patch_print__rename_exact(void) +{ + patch_print_from_patchfile(PATCH_RENAME_EXACT, + strlen(PATCH_RENAME_EXACT)); +} + +void test_patch_print__rename_similar(void) +{ + patch_print_from_patchfile(PATCH_RENAME_SIMILAR, + strlen(PATCH_RENAME_SIMILAR)); +} + +void test_patch_print__rename_exact_quotedname(void) +{ + patch_print_from_patchfile(PATCH_RENAME_EXACT_QUOTEDNAME, + strlen(PATCH_RENAME_EXACT_QUOTEDNAME)); +} + +void test_patch_print__rename_similar_quotedname(void) +{ + patch_print_from_patchfile(PATCH_RENAME_SIMILAR_QUOTEDNAME, + strlen(PATCH_RENAME_SIMILAR_QUOTEDNAME)); +} + +void test_patch_print__modechange_unchanged(void) +{ + patch_print_from_patchfile(PATCH_MODECHANGE_UNCHANGED, + strlen(PATCH_MODECHANGE_UNCHANGED)); +} + +void test_patch_print__modechange_modified(void) +{ + patch_print_from_patchfile(PATCH_MODECHANGE_MODIFIED, + strlen(PATCH_MODECHANGE_MODIFIED)); +} + +void test_patch_print__binary_literal(void) +{ + patch_print_from_patchfile(PATCH_BINARY_LITERAL, + strlen(PATCH_BINARY_LITERAL)); +} + +void test_patch_print__binary_delta(void) +{ + patch_print_from_patchfile(PATCH_BINARY_DELTA, + strlen(PATCH_BINARY_DELTA)); +} + +void test_patch_print__binary_add(void) +{ + patch_print_from_patchfile(PATCH_BINARY_ADD, + strlen(PATCH_BINARY_ADD)); +} + +void test_patch_print__binary_delete(void) +{ + patch_print_from_patchfile(PATCH_BINARY_DELETE, + strlen(PATCH_BINARY_DELETE)); +} + +void test_patch_print__not_reversible(void) +{ + patch_print_from_patchfile(PATCH_BINARY_NOT_REVERSIBLE, + strlen(PATCH_BINARY_NOT_REVERSIBLE)); +} + +void test_patch_print__binary_not_shown(void) +{ + patch_print_from_patchfile(PATCH_BINARY_NOT_PRINTED, + strlen(PATCH_BINARY_NOT_PRINTED)); +} diff --git a/tests/rebase/abort.c b/tests/rebase/abort.c index c4b3890bc..d8891fb1d 100644 --- a/tests/rebase/abort.c +++ b/tests/rebase/abort.c @@ -19,17 +19,15 @@ void test_rebase_abort__cleanup(void) cl_git_sandbox_cleanup(); } -static void test_abort(git_annotated_commit *branch, git_annotated_commit *onto) +static void ensure_aborted( + git_annotated_commit *branch, + git_annotated_commit *onto) { - git_rebase *rebase; git_reference *head_ref, *branch_ref = NULL; git_status_list *statuslist; git_reflog *reflog; const git_reflog_entry *reflog_entry; - cl_git_pass(git_rebase_open(&rebase, repo, NULL)); - cl_git_pass(git_rebase_abort(rebase)); - cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); /* Make sure the refs are updated appropriately */ @@ -58,6 +56,18 @@ static void test_abort(git_annotated_commit *branch, git_annotated_commit *onto) git_reflog_free(reflog); git_reference_free(head_ref); git_reference_free(branch_ref); +} + +static void test_abort( + git_annotated_commit *branch, git_annotated_commit *onto) +{ + git_rebase *rebase; + + cl_git_pass(git_rebase_open(&rebase, repo, NULL)); + cl_git_pass(git_rebase_abort(rebase)); + + ensure_aborted(branch, onto); + git_rebase_free(rebase); } @@ -86,19 +96,110 @@ void test_rebase_abort__merge(void) git_rebase_free(rebase); } +void test_rebase_abort__merge_immediately_after_init(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *onto_ref; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_git_pass(git_rebase_abort(rebase)); + ensure_aborted(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_id(void) +{ + git_rebase *rebase; + git_oid branch_id, onto_id; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_revspec(void) +{ + git_rebase *rebase; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_annotated_commit_from_revspec(&branch_head, repo, "b146bd7")); + cl_git_pass(git_annotated_commit_from_revspec(&onto_head, repo, "efad0b1")); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + test_abort(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + +void test_rebase_abort__merge_by_id_immediately_after_init(void) +{ + git_rebase *rebase; + git_oid branch_id, onto_id; + git_annotated_commit *branch_head, *onto_head; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, NULL, onto_head, NULL)); + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_git_pass(git_rebase_abort(rebase)); + ensure_aborted(branch_head, onto_head); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + + git_rebase_free(rebase); +} + void test_rebase_abort__detached_head(void) { git_rebase *rebase; - git_oid branch_id; - git_reference *onto_ref; + git_oid branch_id, onto_id; git_signature *signature; git_annotated_commit *branch_head, *onto_head; git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64"); - cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); - cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); cl_git_pass(git_signature_new(&signature, "Rebaser", "rebaser@example.com", 1404157834, -400)); @@ -112,7 +213,6 @@ void test_rebase_abort__detached_head(void) git_annotated_commit_free(branch_head); git_annotated_commit_free(onto_head); - git_reference_free(onto_ref); git_rebase_free(rebase); } diff --git a/tests/rebase/inmemory.c b/tests/rebase/inmemory.c index d5d89c719..367d6b3ac 100644 --- a/tests/rebase/inmemory.c +++ b/tests/rebase/inmemory.c @@ -5,15 +5,20 @@ #include static git_repository *repo; +static git_signature *signature; // Fixture setup and teardown void test_rebase_inmemory__initialize(void) { repo = cl_git_sandbox_init("rebase"); + + cl_git_pass(git_signature_new(&signature, + "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); } void test_rebase_inmemory__cleanup(void) { + git_signature_free(signature); cl_git_sandbox_cleanup(); } @@ -53,14 +58,10 @@ void test_rebase_inmemory__can_resolve_conflicts(void) git_rebase_operation *rebase_operation; git_status_list *status_list; git_oid pick_id, commit_id, expected_commit_id; - git_signature *signature; git_index *rebase_index, *repo_index; git_index_entry resolution = {{0}}; git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; - cl_git_pass(git_signature_new(&signature, - "Rebaser", "rebaser@rebaser.rb", 1405694510, 0)); - opts.inmemory = true; cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/asparagus")); @@ -104,7 +105,6 @@ void test_rebase_inmemory__can_resolve_conflicts(void) cl_git_pass(git_oid_fromstr(&expected_commit_id, "db7af47222181e548810da2ab5fec0e9357c5637")); cl_assert_equal_oid(&commit_id, &expected_commit_id); - git_signature_free(signature); git_status_list_free(status_list); git_annotated_commit_free(branch_head); git_annotated_commit_free(upstream_head); @@ -114,3 +114,97 @@ void test_rebase_inmemory__can_resolve_conflicts(void) git_index_free(rebase_index); git_rebase_free(rebase); } + +void test_rebase_inmemory__no_common_ancestor(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_final_id; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); + cl_assert_equal_oid(&expected_final_id, &commit_id); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_inmemory__with_directories(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id; + git_commit *commit; + git_rebase_options opts = GIT_REBASE_OPTIONS_INIT; + + opts.inmemory = true; + + git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, &opts)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} diff --git a/tests/rebase/merge.c b/tests/rebase/merge.c index c60113b64..7b2d6876c 100644 --- a/tests/rebase/merge.c +++ b/tests/rebase/merge.c @@ -1,4 +1,5 @@ #include "clar_libgit2.h" +#include "git2/checkout.h" #include "git2/rebase.h" #include "posix.h" #include "signature.h" @@ -252,6 +253,63 @@ void test_rebase_merge__commit(void) git_rebase_free(rebase); } +void test_rebase_merge__commit_with_id(void) +{ + git_rebase *rebase; + git_oid branch_id, upstream_id; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id, parent_id; + git_signature *author; + git_commit *commit; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + + git_oid_fromstr(&parent_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_assert_equal_i(1, git_commit_parentcount(commit)); + cl_assert_equal_oid(&parent_id, git_commit_parent_id(commit, 0)); + + git_oid_fromstr(&tree_id, "4461379789c777d2a6c1f2ee0e9d6c86731b9992"); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + cl_assert_equal_s(NULL, git_commit_message_encoding(commit)); + cl_assert_equal_s("Modification 1 to beef\n", git_commit_message(commit)); + + cl_git_pass(git_signature_new(&author, + "Edward Thomson", "ethomson@edwardthomson.com", 1405621769, 0-(4*60))); + cl_assert(git_signature__equal(author, git_commit_author(commit))); + + cl_assert(git_signature__equal(signature, git_commit_committer(commit))); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&parent_id, git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: Modification 1 to beef", git_reflog_entry_message(reflog_entry)); + + git_reflog_free(reflog); + git_signature_free(author); + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_rebase_free(rebase); +} + void test_rebase_merge__blocked_when_dirty(void) { git_rebase *rebase; @@ -418,6 +476,157 @@ void test_rebase_merge__finish(void) git_rebase_free(rebase); } +void test_rebase_merge__detached_finish(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref, *head_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + int error; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_repository_set_head_detached_from_annotated(repo, branch_head)); + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + git_checkout_head(repo, &opts); + + cl_git_pass(git_rebase_init(&rebase, repo, NULL, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REF_OID, git_reference_type(head_ref)); + + /* Make sure the reflogs are updated appropriately */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(git_annotated_commit_id(upstream_head), git_reflog_entry_id_old(reflog_entry)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + + git_reflog_free(reflog); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(head_ref); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__finish_with_ids(void) +{ + git_rebase *rebase; + git_reference *head_ref; + git_oid branch_id, upstream_id; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id; + git_reflog *reflog; + const git_reflog_entry *reflog_entry; + int error; + + cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); + cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail(error = git_rebase_next(&rebase_operation, rebase)); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&head_ref, repo, "HEAD")); + cl_assert_equal_i(GIT_REF_OID, git_reference_type(head_ref)); + cl_assert_equal_oid(&commit_id, git_reference_target(head_ref)); + + /* reflogs are not updated as if we were operating on proper + * branches. check that the last reflog entry is the rebase. + */ + cl_git_pass(git_reflog_read(&reflog, repo, "HEAD")); + cl_assert(reflog_entry = git_reflog_entry_byindex(reflog, 0)); + cl_assert_equal_oid(&commit_id, git_reflog_entry_id_new(reflog_entry)); + cl_assert_equal_s("rebase: Modification 3 to gravy", git_reflog_entry_message(reflog_entry)); + git_reflog_free(reflog); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(head_ref); + git_rebase_free(rebase); +} + +void test_rebase_merge__no_common_ancestor(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, expected_final_id; + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/barley")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_finish(rebase, signature)); + + git_oid_fromstr(&expected_final_id, "71e7ee8d4fe7d8bf0d107355197e0a953dfdb7f3"); + cl_assert_equal_oid(&expected_final_id, &commit_id); + + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + static void test_copy_note( const git_rebase_options *opts, bool should_exist) @@ -595,3 +804,42 @@ void test_rebase_merge__custom_merge_options(void) git_rebase_free(rebase); } +void test_rebase_merge__with_directories(void) +{ + git_rebase *rebase; + git_reference *branch_ref, *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_rebase_operation *rebase_operation; + git_oid commit_id, tree_id; + git_commit *commit; + + git_oid_fromstr(&tree_id, "a4d6d9c3d57308fd8e320cf2525bae8f1adafa57"); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/deep_gravy")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/veal")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_pass(git_rebase_next(&rebase_operation, rebase)); + cl_git_pass(git_rebase_commit(&commit_id, rebase, NULL, signature, + NULL, NULL)); + + cl_git_fail_with(GIT_ITEROVER, git_rebase_next(&rebase_operation, rebase)); + + cl_git_pass(git_commit_lookup(&commit, repo, &commit_id)); + cl_assert_equal_oid(&tree_id, git_commit_tree_id(commit)); + + git_commit_free(commit); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} diff --git a/tests/rebase/setup.c b/tests/rebase/setup.c index 627d3b9de..b07a83af6 100644 --- a/tests/rebase/setup.c +++ b/tests/rebase/setup.c @@ -196,6 +196,115 @@ void test_rebase_setup__merge_onto_and_upstream(void) git_rebase_free(rebase); } +/* git checkout beef && git rebase --merge --onto master gravy veal */ +void test_rebase_setup__merge_onto_upstream_and_branch(void) +{ + git_rebase *rebase; + git_reference *upstream_ref, *branch_ref, *onto_ref; + git_annotated_commit *upstream_head, *branch_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/veal")); + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/gravy")); + cl_git_pass(git_reference_lookup(&onto_ref, repo, "refs/heads/master")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + cl_git_pass(git_annotated_commit_from_ref(&onto_head, repo, onto_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("3e8989b5a16d5258c935d998ef0e6bb139cc4757\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("4cacc6f6e740a5bc64faa33e04b8ef0733d8a127\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("3\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("f87d14a4a236582a0278a916340a793714256864\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(upstream_head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + git_reference_free(upstream_ref); + git_reference_free(branch_ref); + git_reference_free(onto_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge --onto `git rev-parse master` + * `git rev-parse veal` `git rev-parse gravy` + */ +void test_rebase_setup__merge_onto_upstream_and_branch_by_id(void) +{ + git_rebase *rebase; + git_oid upstream_id, branch_id, onto_id; + git_annotated_commit *upstream_head, *branch_head, *onto_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id; + git_checkout_options checkout_opts = GIT_CHECKOUT_OPTIONS_INIT; + + checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_repository_set_head(repo, "refs/heads/beef")); + cl_git_pass(git_checkout_head(repo, &checkout_opts)); + + cl_git_pass(git_oid_fromstr(&upstream_id, "f87d14a4a236582a0278a916340a793714256864")); + cl_git_pass(git_oid_fromstr(&branch_id, "d616d97082eb7bb2dc6f180a7cca940993b7a56f")); + cl_git_pass(git_oid_fromstr(&onto_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_lookup(&onto_head, repo, &onto_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, onto_head, NULL)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("1\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("d616d97082eb7bb2dc6f180a7cca940993b7a56f\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(upstream_head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(onto_head); + git_rebase_free(rebase); +} + /* Ensure merge commits are dropped in a rebase */ /* git checkout veal && git rebase --merge master */ void test_rebase_setup__branch_with_merges(void) @@ -342,6 +451,102 @@ void test_rebase_setup__merge_null_branch_uses_HEAD(void) git_rebase_free(rebase); } +/* git checkout b146bd7608eac53d9bf9e1a6963543588b555c64 && git rebase --merge master */ +void test_rebase_setup__merge_from_detached(void) +{ + git_rebase *rebase; + git_reference *upstream_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid branch_id, head_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&upstream_ref, repo, "refs/heads/master")); + + cl_git_pass(git_oid_fromstr(&branch_id, "b146bd7608eac53d9bf9e1a6963543588b555c64")); + + cl_git_pass(git_annotated_commit_lookup(&branch_head, repo, &branch_id)); + cl_git_pass(git_annotated_commit_from_ref(&upstream_head, repo, upstream_ref)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("master\n", 7, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(upstream_ref); + git_rebase_free(rebase); +} + +/* git checkout beef && git rebase --merge efad0b11c47cb2f0220cbd6f5b0f93bb99064b00 */ +void test_rebase_setup__merge_branch_by_id(void) +{ + git_rebase *rebase; + git_reference *branch_ref; + git_annotated_commit *branch_head, *upstream_head; + git_reference *head; + git_commit *head_commit; + git_oid head_id, upstream_id; + + cl_assert_equal_i(GIT_REPOSITORY_STATE_NONE, git_repository_state(repo)); + + cl_git_pass(git_reference_lookup(&branch_ref, repo, "refs/heads/beef")); + + cl_git_pass(git_oid_fromstr(&upstream_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00")); + + cl_git_pass(git_annotated_commit_from_ref(&branch_head, repo, branch_ref)); + cl_git_pass(git_annotated_commit_lookup(&upstream_head, repo, &upstream_id)); + + cl_git_pass(git_rebase_init(&rebase, repo, branch_head, upstream_head, NULL, NULL)); + + cl_assert_equal_i(GIT_REPOSITORY_STATE_REBASE_MERGE, git_repository_state(repo)); + + git_oid_fromstr(&head_id, "efad0b11c47cb2f0220cbd6f5b0f93bb99064b00"); + cl_git_pass(git_repository_head(&head, repo)); + cl_git_pass(git_reference_peel((git_object **)&head_commit, head, GIT_OBJ_COMMIT)); + cl_assert_equal_oid(&head_id, git_commit_id(head_commit)); + + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/ORIG_HEAD"); + + cl_assert_equal_file("da9c51a23d02d931a486f45ad18cda05cf5d2b94\n", 41, "rebase/.git/rebase-merge/cmt.1"); + cl_assert_equal_file("8d1f13f93c4995760ac07d129246ac1ff64c0be9\n", 41, "rebase/.git/rebase-merge/cmt.2"); + cl_assert_equal_file("3069cc907e6294623e5917ef6de663928c1febfb\n", 41, "rebase/.git/rebase-merge/cmt.3"); + cl_assert_equal_file("588e5d2f04d49707fe4aab865e1deacaf7ef6787\n", 41, "rebase/.git/rebase-merge/cmt.4"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/cmt.5"); + cl_assert_equal_file("5\n", 2, "rebase/.git/rebase-merge/end"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto"); + cl_assert_equal_file("efad0b11c47cb2f0220cbd6f5b0f93bb99064b00\n", 41, "rebase/.git/rebase-merge/onto_name"); + cl_assert_equal_file("b146bd7608eac53d9bf9e1a6963543588b555c64\n", 41, "rebase/.git/rebase-merge/orig-head"); + + git_commit_free(head_commit); + git_reference_free(head); + git_annotated_commit_free(branch_head); + git_annotated_commit_free(upstream_head); + git_reference_free(branch_ref); + git_rebase_free(rebase); +} + static int rebase_is_blocked(void) { git_rebase *rebase = NULL; diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c index 31dec0678..69488e6c7 100644 --- a/tests/refs/branches/create.c +++ b/tests/refs/branches/create.c @@ -65,10 +65,14 @@ void test_refs_branches_create__can_force_create_over_an_existing_branch(void) cl_assert_equal_s("refs/heads/br2", git_reference_name(branch)); } -void test_refs_branches_create__cannot_force_create_over_current_branch(void) +void test_refs_branches_create__cannot_force_create_over_current_branch_in_nonbare_repo(void) { const git_oid *oid; git_reference *branch2; + + /* Default repo for these tests is a bare repo, but this test requires a non-bare one */ + cl_git_sandbox_cleanup(); + repo = cl_git_sandbox_init("testrepo"); retrieve_known_commit(&target, repo); cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); @@ -84,6 +88,26 @@ void test_refs_branches_create__cannot_force_create_over_current_branch(void) git_reference_free(branch2); } +void test_refs_branches_create__can_force_create_over_current_branch_in_bare_repo(void) +{ + const git_oid *oid; + git_reference *branch2; + retrieve_known_commit(&target, repo); + + cl_git_pass(git_branch_lookup(&branch2, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch2)); + cl_assert_equal_i(true, git_branch_is_head(branch2)); + oid = git_commit_id(target); + + cl_git_pass(git_branch_create(&branch, repo, "master", target, 1)); + git_reference_free(branch); + branch = NULL; + cl_git_pass(git_branch_lookup(&branch, repo, "master", GIT_BRANCH_LOCAL)); + cl_assert_equal_s("refs/heads/master", git_reference_name(branch)); + cl_git_pass(git_oid_cmp(git_reference_target(branch), oid)); + git_reference_free(branch2); +} + void test_refs_branches_create__creating_a_branch_with_an_invalid_name_returns_EINVALIDSPEC(void) { retrieve_known_commit(&target, repo); diff --git a/tests/refs/dup.c b/tests/refs/dup.c new file mode 100644 index 000000000..0fc635a7a --- /dev/null +++ b/tests/refs/dup.c @@ -0,0 +1,40 @@ +#include "clar_libgit2.h" +#include "refs.h" + +static git_repository *g_repo; + +void test_refs_dup__initialize(void) +{ + g_repo = cl_git_sandbox_init("testrepo.git"); +} + +void test_refs_dup__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_refs_dup__direct(void) +{ + git_reference *a, *b; + + cl_git_pass(git_reference_lookup(&a, g_repo, "refs/heads/master")); + cl_git_pass(git_reference_dup(&b, a)); + + cl_assert(git_reference_cmp(a, b) == 0); + + git_reference_free(b); + git_reference_free(a); +} + +void test_refs_dup__symbolic(void) +{ + git_reference *a, *b; + + cl_git_pass(git_reference_lookup(&a, g_repo, "HEAD")); + cl_git_pass(git_reference_dup(&b, a)); + + cl_assert(git_reference_cmp(a, b) == 0); + + git_reference_free(b); + git_reference_free(a); +} diff --git a/tests/repo/discover.c b/tests/repo/discover.c index 358daee9f..48aa27581 100644 --- a/tests/repo/discover.c +++ b/tests/repo/discover.c @@ -9,6 +9,7 @@ #define SUB_REPOSITORY_FOLDER_NAME "sub_repo" #define SUB_REPOSITORY_FOLDER DISCOVER_FOLDER "/" SUB_REPOSITORY_FOLDER_NAME +#define SUB_REPOSITORY_GITDIR SUB_REPOSITORY_FOLDER "/.git" #define SUB_REPOSITORY_FOLDER_SUB SUB_REPOSITORY_FOLDER "/sub" #define SUB_REPOSITORY_FOLDER_SUB_SUB SUB_REPOSITORY_FOLDER_SUB "/subsub" #define SUB_REPOSITORY_FOLDER_SUB_SUB_SUB SUB_REPOSITORY_FOLDER_SUB_SUB "/subsubsub" @@ -24,20 +25,26 @@ #define ALTERNATE_NOT_FOUND_FOLDER DISCOVER_FOLDER "/alternate_not_found_repo" static void ensure_repository_discover(const char *start_path, - const char *ceiling_dirs, - git_buf *expected_path) + const char *ceiling_dirs, + const char *expected_path) { - git_buf found_path = GIT_BUF_INIT; + git_buf found_path = GIT_BUF_INIT, resolved = GIT_BUF_INIT; + + git_buf_attach(&resolved, p_realpath(expected_path, NULL), 0); + cl_assert(resolved.size > 0); + cl_git_pass(git_path_to_dir(&resolved)); cl_git_pass(git_repository_discover(&found_path, start_path, 0, ceiling_dirs)); - //across_fs is always 0 as we can't automate the filesystem change tests - cl_assert_equal_s(found_path.ptr, expected_path->ptr); + + cl_assert_equal_s(found_path.ptr, resolved.ptr); + + git_buf_free(&resolved); git_buf_free(&found_path); } static void write_file(const char *path, const char *content) { git_file file; - int error; + int error; if (git_path_exists(path)) { cl_git_pass(p_unlink(path)); @@ -68,42 +75,30 @@ static void append_ceiling_dir(git_buf *ceiling_dirs, const char *path) cl_assert(git_buf_oom(ceiling_dirs) == 0); } -void test_repo_discover__0(void) +static git_buf discovered; +static git_buf ceiling_dirs; + +void test_repo_discover__initialize(void) { - // test discover git_repository *repo; - git_buf ceiling_dirs_buf = GIT_BUF_INIT, repository_path = GIT_BUF_INIT, - sub_repository_path = GIT_BUF_INIT, found_path = GIT_BUF_INIT; - const char *ceiling_dirs; const mode_t mode = 0777; - git_futils_mkdir_r(DISCOVER_FOLDER, mode); - append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER); - ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs)); + git_buf_init(&discovered, 0); + git_buf_init(&ceiling_dirs, 0); + append_ceiling_dir(&ceiling_dirs, TEMP_REPO_FOLDER); cl_git_pass(git_repository_init(&repo, DISCOVER_FOLDER, 1)); - cl_git_pass(git_repository_discover(&repository_path, DISCOVER_FOLDER, 0, ceiling_dirs)); git_repository_free(repo); cl_git_pass(git_repository_init(&repo, SUB_REPOSITORY_FOLDER, 0)); cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); - cl_git_pass(git_repository_discover(&sub_repository_path, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs)); - cl_git_pass(git_futils_mkdir_r(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, mode)); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs, &sub_repository_path); cl_git_pass(git_futils_mkdir_r(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, mode)); write_file(REPOSITORY_ALTERNATE_FOLDER "/" DOT_GIT, "gitdir: ../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB "/" DOT_GIT, "gitdir: ../../../" SUB_REPOSITORY_FOLDER_NAME "/" DOT_GIT); write_file(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB "/" DOT_GIT, "gitdir: ../../../../"); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path); cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode)); write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); @@ -113,41 +108,94 @@ void test_repo_discover__0(void) write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, mode)); write_file(ALTERNATE_NOT_FOUND_FOLDER "/" DOT_GIT, "gitdir: a_repository_that_surely_does_not_exist"); - cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs)); - cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs)); - cl_git_fail(git_repository_discover(&found_path, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs)); - append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER_SUB); - ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); + git_repository_free(repo); +} + +void test_repo_discover__cleanup(void) +{ + git_buf_free(&discovered); + git_buf_free(&ceiling_dirs); + cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_repo_discover__discovering_repo_with_exact_path_succeeds(void) +{ + cl_git_pass(git_repository_discover(&discovered, DISCOVER_FOLDER, 0, ceiling_dirs.ptr)); + cl_git_pass(git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__discovering_nonexistent_dir_fails(void) +{ + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, DISCOVER_FOLDER "-nonexistent", 0, NULL)); +} + +void test_repo_discover__discovering_repo_with_subdirectory_succeeds(void) +{ + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); +} + +void test_repo_discover__discovering_repository_with_alternative_gitdir_succeeds(void) +{ + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); +} + +void test_repo_discover__discovering_repository_with_malformed_alternative_gitdir_fails(void) +{ + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER1, 0, ceiling_dirs.ptr)); + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER2, 0, ceiling_dirs.ptr)); + cl_git_fail(git_repository_discover(&discovered, ALTERNATE_MALFORMED_FOLDER3, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, ALTERNATE_NOT_FOUND_FOLDER, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__discovering_repository_with_ceiling(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER_SUB); /* this must pass as ceiling_directories cannot prevent the current * working directory to be checked */ - ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs, &sub_repository_path); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs)); + ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); - append_ceiling_dir(&ceiling_dirs_buf, SUB_REPOSITORY_FOLDER); - ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); - - //this must pass as ceiling_directories cannot predent the current - //working directory to be checked - ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs, &sub_repository_path); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs)); - cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&found_path, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs)); - - //.gitfile redirection should not be affected by ceiling directories - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); - ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path); - - cl_git_pass(git_futils_rmdir_r(TEMP_REPO_FOLDER, NULL, GIT_RMDIR_REMOVE_FILES)); - git_repository_free(repo); - git_buf_free(&ceiling_dirs_buf); - git_buf_free(&repository_path); - git_buf_free(&sub_repository_path); + ensure_repository_discover(SUB_REPOSITORY_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); } +void test_repo_discover__other_ceiling(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); + + /* this must pass as ceiling_directories cannot predent the current + * working directory to be checked */ + ensure_repository_discover(SUB_REPOSITORY_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB, 0, ceiling_dirs.ptr)); + cl_assert_equal_i(GIT_ENOTFOUND, git_repository_discover(&discovered, SUB_REPOSITORY_FOLDER_SUB_SUB_SUB, 0, ceiling_dirs.ptr)); +} + +void test_repo_discover__ceiling_should_not_affect_gitdir_redirection(void) +{ + append_ceiling_dir(&ceiling_dirs, SUB_REPOSITORY_FOLDER); + + /* gitfile redirection should not be affected by ceiling directories */ + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); + ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs.ptr, DISCOVER_FOLDER); +} + +void test_repo_discover__discovery_starting_at_file_succeeds(void) +{ + int fd; + + cl_assert((fd = p_creat(SUB_REPOSITORY_FOLDER "/file", 0600)) >= 0); + cl_assert(p_close(fd) == 0); + + ensure_repository_discover(SUB_REPOSITORY_FOLDER "/file", ceiling_dirs.ptr, SUB_REPOSITORY_GITDIR); +} diff --git a/tests/repo/env.c b/tests/repo/env.c new file mode 100644 index 000000000..5a89c0d49 --- /dev/null +++ b/tests/repo/env.c @@ -0,0 +1,277 @@ +#include "clar_libgit2.h" +#include "fileops.h" +#include "sysdir.h" +#include + +static void clear_git_env(void) +{ + cl_setenv("GIT_DIR", NULL); + cl_setenv("GIT_CEILING_DIRECTORIES", NULL); + cl_setenv("GIT_INDEX_FILE", NULL); + cl_setenv("GIT_NAMESPACE", NULL); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + cl_setenv("GIT_WORK_TREE", NULL); + cl_setenv("GIT_COMMON_DIR", NULL); +} + +void test_repo_env__initialize(void) +{ + clear_git_env(); +} + +void test_repo_env__cleanup(void) +{ + cl_git_sandbox_cleanup(); + + if (git_path_isdir("attr")) + git_futils_rmdir_r("attr", NULL, GIT_RMDIR_REMOVE_FILES); + if (git_path_isdir("testrepo.git")) + git_futils_rmdir_r("testrepo.git", NULL, GIT_RMDIR_REMOVE_FILES); + if (git_path_isdir("peeled.git")) + git_futils_rmdir_r("peeled.git", NULL, GIT_RMDIR_REMOVE_FILES); + + clear_git_env(); +} + +static int GIT_FORMAT_PRINTF(2, 3) cl_setenv_printf(const char *name, const char *fmt, ...) +{ + int ret; + va_list args; + git_buf buf = GIT_BUF_INIT; + + va_start(args, fmt); + cl_git_pass(git_buf_vprintf(&buf, fmt, args)); + va_end(args); + + ret = cl_setenv(name, git_buf_cstr(&buf)); + git_buf_free(&buf); + return ret; +} + +/* Helper functions for test_repo_open__env, passing through the file and line + * from the caller rather than those of the helper. The expression strings + * distinguish between the possible failures within the helper. */ + +static void env_pass_(const char *path, const char *file, int line) +{ + git_repository *repo; + cl_git_pass_(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, line); + cl_git_pass_(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, line); + cl_assert_at_line(git__suffixcmp(git_repository_path(repo), "attr/.git/") == 0, file, line); + cl_assert_at_line(git__suffixcmp(git_repository_workdir(repo), "attr/") == 0, file, line); + cl_assert_at_line(!git_repository_is_bare(repo), file, line); + git_repository_free(repo); +} +#define env_pass(path) env_pass_((path), __FILE__, __LINE__) + +#define cl_git_fail_at_line(expr, file, line) clar__assert((expr) < 0, file, line, "Expected function call to fail: " #expr, NULL, 1) + +static void env_fail_(const char *path, const char *file, int line) +{ + git_repository *repo; + cl_git_fail_at_line(git_repository_open_ext(NULL, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, line); + cl_git_fail_at_line(git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, line); +} +#define env_fail(path) env_fail_((path), __FILE__, __LINE__) + +static void env_cd_( + const char *path, + void (*passfail_)(const char *, const char *, int), + const char *file, int line) +{ + git_buf cwd_buf = GIT_BUF_INIT; + cl_git_pass(git_path_prettify_dir(&cwd_buf, ".", NULL)); + cl_must_pass(p_chdir(path)); + passfail_(NULL, file, line); + cl_must_pass(p_chdir(git_buf_cstr(&cwd_buf))); + git_buf_free(&cwd_buf); +} +#define env_cd_pass(path) env_cd_((path), env_pass_, __FILE__, __LINE__) +#define env_cd_fail(path) env_cd_((path), env_fail_, __FILE__, __LINE__) + +static void env_check_objects_(bool a, bool t, bool p, const char *file, int line) +{ + git_repository *repo; + git_oid oid_a, oid_t, oid_p; + git_object *object; + cl_git_pass(git_oid_fromstr(&oid_a, "45141a79a77842c59a63229403220a4e4be74e3d")); + cl_git_pass(git_oid_fromstr(&oid_t, "1385f264afb75a56a5bec74243be9b367ba4ca08")); + cl_git_pass(git_oid_fromstr(&oid_p, "0df1a5865c8abfc09f1f2182e6a31be550e99f07")); + cl_git_pass_(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL), file, line); + + if (a) { + cl_git_pass_(git_object_lookup(&object, repo, &oid_a, GIT_OBJ_BLOB), file, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_a, GIT_OBJ_BLOB), file, line); + } + + if (t) { + cl_git_pass_(git_object_lookup(&object, repo, &oid_t, GIT_OBJ_BLOB), file, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_t, GIT_OBJ_BLOB), file, line); + } + + if (p) { + cl_git_pass_(git_object_lookup(&object, repo, &oid_p, GIT_OBJ_COMMIT), file, line); + git_object_free(object); + } else { + cl_git_fail_at_line(git_object_lookup(&object, repo, &oid_p, GIT_OBJ_COMMIT), file, line); + } + + git_repository_free(repo); +} +#define env_check_objects(a, t, t2) env_check_objects_((a), (t), (t2), __FILE__, __LINE__) + +void test_repo_env__open(void) +{ + git_repository *repo = NULL; + git_buf repo_dir_buf = GIT_BUF_INIT; + const char *repo_dir = NULL; + git_index *index = NULL; + const char *t_obj = "testrepo.git/objects"; + const char *p_obj = "peeled.git/objects"; + + clear_git_env(); + + cl_fixture_sandbox("attr"); + cl_fixture_sandbox("testrepo.git"); + cl_fixture_sandbox("peeled.git"); + cl_git_pass(p_rename("attr/.gitted", "attr/.git")); + + cl_git_pass(git_path_prettify_dir(&repo_dir_buf, "attr", NULL)); + repo_dir = git_buf_cstr(&repo_dir_buf); + + /* GIT_DIR that doesn't exist */ + cl_setenv("GIT_DIR", "does-not-exist"); + env_fail(NULL); + /* Explicit start_path overrides GIT_DIR */ + env_pass("attr"); + env_pass("attr/.git"); + env_pass("attr/sub"); + env_pass("attr/sub/sub"); + + /* GIT_DIR with relative paths */ + cl_setenv("GIT_DIR", "attr/.git"); + env_pass(NULL); + cl_setenv("GIT_DIR", "attr"); + env_fail(NULL); + cl_setenv("GIT_DIR", "attr/sub"); + env_fail(NULL); + cl_setenv("GIT_DIR", "attr/sub/sub"); + env_fail(NULL); + + /* GIT_DIR with absolute paths */ + cl_setenv_printf("GIT_DIR", "%s/.git", repo_dir); + env_pass(NULL); + cl_setenv("GIT_DIR", repo_dir); + env_fail(NULL); + cl_setenv_printf("GIT_DIR", "%s/sub", repo_dir); + env_fail(NULL); + cl_setenv_printf("GIT_DIR", "%s/sub/sub", repo_dir); + env_fail(NULL); + cl_setenv("GIT_DIR", NULL); + + /* Searching from the current directory */ + env_cd_pass("attr"); + env_cd_pass("attr/.git"); + env_cd_pass("attr/sub"); + env_cd_pass("attr/sub/sub"); + + /* A ceiling directory blocks searches from ascending into that + * directory, but doesn't block the start_path itself. */ + cl_setenv("GIT_CEILING_DIRECTORIES", repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub", repo_dir); + env_cd_pass("attr"); + env_cd_pass("attr/sub"); + env_cd_fail("attr/sub/sub"); + + /* Multiple ceiling directories */ + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "123%c%s/sub%cabc", + GIT_PATH_LIST_SEPARATOR, repo_dir, GIT_PATH_LIST_SEPARATOR); + env_cd_pass("attr"); + env_cd_pass("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s/sub%c%s", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv_printf("GIT_CEILING_DIRECTORIES", "%s%c%s/sub/sub", + repo_dir, GIT_PATH_LIST_SEPARATOR, repo_dir); + env_cd_pass("attr"); + env_cd_fail("attr/sub"); + env_cd_fail("attr/sub/sub"); + + cl_setenv("GIT_CEILING_DIRECTORIES", NULL); + + /* Index files */ + cl_setenv("GIT_INDEX_FILE", cl_fixture("gitgit.index")); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_git_pass(git_repository_index(&index, repo)); + cl_assert_equal_s(git_index_path(index), cl_fixture("gitgit.index")); + cl_assert_equal_i(git_index_entrycount(index), 1437); + git_index_free(index); + git_repository_free(repo); + cl_setenv("GIT_INDEX_FILE", NULL); + + /* Namespaces */ + cl_setenv("GIT_NAMESPACE", "some-namespace"); + cl_git_pass(git_repository_open_ext(&repo, "attr", GIT_REPOSITORY_OPEN_FROM_ENV, NULL)); + cl_assert_equal_s(git_repository_get_namespace(repo), "some-namespace"); + git_repository_free(repo); + cl_setenv("GIT_NAMESPACE", NULL); + + /* Object directories and alternates */ + env_check_objects(true, false, false); + + cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); + env_check_objects(false, true, false); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", t_obj); + env_check_objects(true, true, false); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_setenv("GIT_OBJECT_DIRECTORY", p_obj); + env_check_objects(false, false, true); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv("GIT_OBJECT_DIRECTORY", t_obj); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", p_obj); + env_check_objects(false, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + cl_setenv("GIT_OBJECT_DIRECTORY", NULL); + + cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", + "%s%c%s", t_obj, GIT_PATH_LIST_SEPARATOR, p_obj); + env_check_objects(true, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_setenv_printf("GIT_ALTERNATE_OBJECT_DIRECTORIES", + "%s%c%s", p_obj, GIT_PATH_LIST_SEPARATOR, t_obj); + env_check_objects(true, true, true); + cl_setenv("GIT_ALTERNATE_OBJECT_DIRECTORIES", NULL); + + cl_fixture_cleanup("peeled.git"); + cl_fixture_cleanup("testrepo.git"); + cl_fixture_cleanup("attr"); + + git_buf_free(&repo_dir_buf); + + clear_git_env(); +} diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c deleted file mode 100644 index 6b5795b9c..000000000 --- a/tests/repo/iterator.c +++ /dev/null @@ -1,1549 +0,0 @@ -#include "clar_libgit2.h" -#include "iterator.h" -#include "repository.h" -#include "fileops.h" -#include - -static git_repository *g_repo; - -void test_repo_iterator__initialize(void) -{ -} - -void test_repo_iterator__cleanup(void) -{ - cl_git_sandbox_cleanup(); - g_repo = NULL; -} - -static void expect_iterator_items( - git_iterator *i, - int expected_flat, - const char **expected_flat_paths, - int expected_total, - const char **expected_total_paths) -{ - const git_index_entry *entry; - int count, error; - int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); - bool v = false; - - if (expected_flat < 0) { v = true; expected_flat = -expected_flat; } - if (expected_total < 0) { v = true; expected_total = -expected_total; } - - if (v) fprintf(stderr, "== %s ==\n", no_trees ? "notrees" : "trees"); - - count = 0; - - while (!git_iterator_advance(&entry, i)) { - if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); - - if (no_trees) - cl_assert(entry->mode != GIT_FILEMODE_TREE); - - if (expected_flat_paths) { - const char *expect_path = expected_flat_paths[count]; - size_t expect_len = strlen(expect_path); - - cl_assert_equal_s(expect_path, entry->path); - - if (expect_path[expect_len - 1] == '/') - cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); - else - cl_assert(entry->mode != GIT_FILEMODE_TREE); - } - - if (++count > expected_flat) - break; - } - - cl_assert_equal_i(expected_flat, count); - - cl_git_pass(git_iterator_reset(i, NULL, NULL)); - - count = 0; - cl_git_pass(git_iterator_current(&entry, i)); - - if (v) fprintf(stderr, "-- %s --\n", no_trees ? "notrees" : "trees"); - - while (entry != NULL) { - if (v) fprintf(stderr, " %s %07o\n", entry->path, (int)entry->mode); - - if (no_trees) - cl_assert(entry->mode != GIT_FILEMODE_TREE); - - if (expected_total_paths) { - const char *expect_path = expected_total_paths[count]; - size_t expect_len = strlen(expect_path); - - cl_assert_equal_s(expect_path, entry->path); - - if (expect_path[expect_len - 1] == '/') - cl_assert_equal_i(GIT_FILEMODE_TREE, entry->mode); - else - cl_assert(entry->mode != GIT_FILEMODE_TREE); - } - - if (entry->mode == GIT_FILEMODE_TREE) { - error = git_iterator_advance_into(&entry, i); - - /* could return NOTFOUND if directory is empty */ - cl_assert(!error || error == GIT_ENOTFOUND); - - if (error == GIT_ENOTFOUND) { - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - } else { - error = git_iterator_advance(&entry, i); - cl_assert(!error || error == GIT_ITEROVER); - } - - if (++count > expected_total) - break; - } - - cl_assert_equal_i(expected_total, count); -} - -/* Index contents (including pseudotrees): - * - * 0: a 5: F 10: k/ 16: L/ - * 1: B 6: g 11: k/1 17: L/1 - * 2: c 7: H 12: k/a 18: L/a - * 3: D 8: i 13: k/B 19: L/B - * 4: e 9: J 14: k/c 20: L/c - * 15: k/D 21: L/D - * - * 0: B 5: L/ 11: a 16: k/ - * 1: D 6: L/1 12: c 17: k/1 - * 2: F 7: L/B 13: e 18: k/B - * 3: H 8: L/D 14: g 19: k/D - * 4: J 9: L/a 15: i 20: k/a - * 10: L/c 21: k/c - */ - -void test_repo_iterator__index(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* autoexpand with no tree entries for index */ - cl_git_pass(git_iterator_for_index(&i, g_repo, index, NULL)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); - - git_index_free(index); -} - -void test_repo_iterator__index_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - int caps; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - caps = git_index_caps(index); - - /* force case sensitivity */ - cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); - - /* autoexpand with no tree entries over range */ - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* force case insensitivity */ - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); - - /* autoexpand with no tree entries over range */ - i_opts.flags = 0; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); - - cl_git_pass(git_index_set_caps(index, caps)); - git_index_free(index); -} - -void test_repo_iterator__tree(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_tree *head; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, NULL)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); - - git_tree_free(head); -} - -void test_repo_iterator__tree_icase(void) -{ - git_iterator *i; - git_tree *head; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - /* auto expand with no tree entries */ - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - /* auto expand with tree entries */ - i_opts.start = "c"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); - - git_tree_free(head); -} - -void test_repo_iterator__tree_more(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_tree *head; - static const char *expect_basic[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL, - }; - static const char *expect_trees[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/", - "subdir/current_file", - "subdir/deleted_file", - "subdir/modified_file", - NULL, - }; - static const char *expect_noauto[] = { - "current_file", - "file_deleted", - "modified_file", - "staged_changes", - "staged_changes_file_deleted", - "staged_changes_modified_file", - "staged_delete_file_deleted", - "staged_delete_modified_file", - "subdir.txt", - "subdir/", - NULL - }; - - g_repo = cl_git_sandbox_init("status"); - - cl_git_pass(git_repository_head_tree(&head, g_repo)); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, NULL)); - expect_iterator_items(i, 12, expect_basic, 12, expect_basic); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 13, expect_trees, 13, expect_trees); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); - expect_iterator_items(i, 10, expect_noauto, 13, expect_trees); - git_iterator_free(i); - - git_tree_free(head); -} - -/* "b=name,t=name", blob_id, tree_id */ -static void build_test_tree( - git_oid *out, git_repository *repo, const char *fmt, ...) -{ - git_oid *id; - git_treebuilder *builder; - const char *scan = fmt, *next; - char type, delimiter; - git_filemode_t mode = GIT_FILEMODE_BLOB; - git_buf name = GIT_BUF_INIT; - va_list arglist; - - cl_git_pass(git_treebuilder_new(&builder, repo, NULL)); /* start builder */ - - va_start(arglist, fmt); - while (*scan) { - switch (type = *scan++) { - case 't': case 'T': mode = GIT_FILEMODE_TREE; break; - case 'b': case 'B': mode = GIT_FILEMODE_BLOB; break; - default: - cl_assert(type == 't' || type == 'T' || type == 'b' || type == 'B'); - } - - delimiter = *scan++; /* read and skip delimiter */ - for (next = scan; *next && *next != delimiter; ++next) - /* seek end */; - cl_git_pass(git_buf_set(&name, scan, (size_t)(next - scan))); - for (scan = next; *scan && (*scan == delimiter || *scan == ','); ++scan) - /* skip delimiter and optional comma */; - - id = va_arg(arglist, git_oid *); - - cl_git_pass(git_treebuilder_insert(NULL, builder, name.ptr, id, mode)); - } - va_end(arglist); - - cl_git_pass(git_treebuilder_write(out, builder)); - - git_treebuilder_free(builder); - git_buf_free(&name); -} - -void test_repo_iterator__tree_case_conflicts_0(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, biga_id, littlea_id, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/1.file", "A/3.file", "a/2.file", "a/4.file" }; - const char *expect_ci[] = { - "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; - const char *expect_cs_trees[] = { - "A/", "A/1.file", "A/3.file", "a/", "a/2.file", "a/4.file" }; - const char *expect_ci_trees[] = { - "A/", "A/1.file", "a/2.file", "A/3.file", "a/4.file" }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - /* create tree with: A/1.file, A/3.file, a/2.file, a/4.file */ - build_test_tree( - &biga_id, g_repo, "b|1.file|,b|3.file|", &blob_id, &blob_id); - build_test_tree( - &littlea_id, g_repo, "b|2.file|,b|4.file|", &blob_id, &blob_id); - build_test_tree( - &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_cs, 4, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_ci, 4, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_cs_trees, 6, expect_cs_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 5, expect_ci_trees, 5, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_repo_iterator__tree_case_conflicts_1(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, Ab_id, biga_id, littlea_id, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/a", "A/b/1", "A/c", "a/C", "a/a", "a/b" }; - const char *expect_ci[] = { - "A/a", "a/b", "A/b/1", "A/c" }; - const char *expect_cs_trees[] = { - "A/", "A/a", "A/b/", "A/b/1", "A/c", "a/", "a/C", "a/a", "a/b" }; - const char *expect_ci_trees[] = { - "A/", "A/a", "a/b", "A/b/", "A/b/1", "A/c" }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - /* create: A/a A/b/1 A/c a/a a/b a/C */ - build_test_tree(&Ab_id, g_repo, "b|1|", &blob_id); - build_test_tree( - &biga_id, g_repo, "b|a|,t|b|,b|c|", &blob_id, &Ab_id, &blob_id); - build_test_tree( - &littlea_id, g_repo, "b|a|,b|b|,b|C|", &blob_id, &blob_id, &blob_id); - build_test_tree( - &tree_id, g_repo, "t|A|,t|a|", &biga_id, &littlea_id); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_cs, 6, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 4, expect_ci, 4, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 9, expect_cs_trees, 9, expect_cs_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 6, expect_ci_trees, 6, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_repo_iterator__tree_case_conflicts_2(void) -{ - const char *blob_sha = "d44e18fb93b7107b5cd1b95d601591d77869a1b6"; - git_tree *tree; - git_oid blob_id, d1, d2, c1, c2, b1, b2, a1, a2, tree_id; - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - const char *expect_cs[] = { - "A/B/C/D/16", "A/B/C/D/foo", "A/B/C/d/15", "A/B/C/d/FOO", - "A/B/c/D/14", "A/B/c/D/foo", "A/B/c/d/13", "A/B/c/d/FOO", - "A/b/C/D/12", "A/b/C/D/foo", "A/b/C/d/11", "A/b/C/d/FOO", - "A/b/c/D/10", "A/b/c/D/foo", "A/b/c/d/09", "A/b/c/d/FOO", - "a/B/C/D/08", "a/B/C/D/foo", "a/B/C/d/07", "a/B/C/d/FOO", - "a/B/c/D/06", "a/B/c/D/foo", "a/B/c/d/05", "a/B/c/d/FOO", - "a/b/C/D/04", "a/b/C/D/foo", "a/b/C/d/03", "a/b/C/d/FOO", - "a/b/c/D/02", "a/b/c/D/foo", "a/b/c/d/01", "a/b/c/d/FOO", }; - const char *expect_ci[] = { - "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", - "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", - "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", - "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", - "A/B/C/D/foo", }; - const char *expect_ci_trees[] = { - "A/", "A/B/", "A/B/C/", "A/B/C/D/", - "a/b/c/d/01", "a/b/c/D/02", "a/b/C/d/03", "a/b/C/D/04", - "a/B/c/d/05", "a/B/c/D/06", "a/B/C/d/07", "a/B/C/D/08", - "A/b/c/d/09", "A/b/c/D/10", "A/b/C/d/11", "A/b/C/D/12", - "A/B/c/d/13", "A/B/c/D/14", "A/B/C/d/15", "A/B/C/D/16", - "A/B/C/D/foo", }; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_oid_fromstr(&blob_id, blob_sha)); /* lookup blob */ - - build_test_tree(&d1, g_repo, "b|16|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|15|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|14|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|13|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&d1, g_repo, "b|12|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|11|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|10|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|09|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&a1, g_repo, "t|B|,t|b|", &b1, &b2); - - build_test_tree(&d1, g_repo, "b|08|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|07|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|06|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|05|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b1, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&d1, g_repo, "b|04|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|03|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c1, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&d1, g_repo, "b|02|,b|foo|", &blob_id, &blob_id); - build_test_tree(&d2, g_repo, "b|01|,b|FOO|", &blob_id, &blob_id); - build_test_tree(&c2, g_repo, "t|D|,t|d|", &d1, &d2); - build_test_tree(&b2, g_repo, "t|C|,t|c|", &c1, &c2); - - build_test_tree(&a2, g_repo, "t|B|,t|b|", &b1, &b2); - - build_test_tree(&tree_id, g_repo, "t/A/,t/a/", &a1, &a2); - - cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 32, expect_cs, 32, expect_cs); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 17, expect_ci, 17, expect_ci); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 21, expect_ci_trees, 21, expect_ci_trees); - git_iterator_free(i); - - git_tree_free(tree); -} - -void test_repo_iterator__workdir(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 20, NULL, 20, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 22, NULL, 22, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 12, NULL, 22, NULL); - git_iterator_free(i); -} - -void test_repo_iterator__workdir_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 7, NULL, 7, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 5, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 1, NULL, 4, NULL); - git_iterator_free(i); - - /* auto expand with no tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 13, NULL, 13, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - /* auto expand with tree entries */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 14, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 6, NULL, 6, NULL); - git_iterator_free(i); - - /* no auto expand (implies trees included) */ - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 9, NULL, 14, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 1, NULL, 6, NULL); - git_iterator_free(i); -} - -static void build_workdir_tree(const char *root, int dirs, int subs) -{ - int i, j; - char buf[64], sub[64]; - - for (i = 0; i < dirs; ++i) { - if (i % 2 == 0) { - p_snprintf(buf, sizeof(buf), "%s/dir%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); - - p_snprintf(buf, sizeof(buf), "%s/dir%02d/file", root, i); - cl_git_mkfile(buf, buf); - buf[strlen(buf) - 5] = '\0'; - } else { - p_snprintf(buf, sizeof(buf), "%s/DIR%02d", root, i); - cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); - } - - for (j = 0; j < subs; ++j) { - switch (j % 4) { - case 0: p_snprintf(sub, sizeof(sub), "%s/sub%02d", buf, j); break; - case 1: p_snprintf(sub, sizeof(sub), "%s/sUB%02d", buf, j); break; - case 2: p_snprintf(sub, sizeof(sub), "%s/Sub%02d", buf, j); break; - case 3: p_snprintf(sub, sizeof(sub), "%s/SUB%02d", buf, j); break; - } - cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); - - if (j % 2 == 0) { - size_t sublen = strlen(sub); - memcpy(&sub[sublen], "/file", sizeof("/file")); - cl_git_mkfile(sub, sub); - sub[sublen] = '\0'; - } - } - } -} - -void test_repo_iterator__workdir_depth(void) -{ - git_iterator *iter; - git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("icase"); - - build_workdir_tree("icase", 10, 10); - build_workdir_tree("icase/DIR01/sUB01", 50, 0); - build_workdir_tree("icase/dir02/sUB01", 50, 0); - - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - expect_iterator_items(iter, 125, NULL, 125, NULL); - git_iterator_free(iter); - - /* auto expand with tree entries (empty dirs silently skipped) */ - iter_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, NULL, NULL, &iter_opts)); - expect_iterator_items(iter, 337, NULL, 337, NULL); - git_iterator_free(iter); -} - -void test_repo_iterator__fs(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - static const char *expect_base[] = { - "DIR01/Sub02/file", - "DIR01/sub00/file", - "current_file", - "dir00/Sub02/file", - "dir00/file", - "dir00/sub00/file", - "modified_file", - "new_file", - NULL, - }; - static const char *expect_trees[] = { - "DIR01/", - "DIR01/SUB03/", - "DIR01/Sub02/", - "DIR01/Sub02/file", - "DIR01/sUB01/", - "DIR01/sub00/", - "DIR01/sub00/file", - "current_file", - "dir00/", - "dir00/SUB03/", - "dir00/Sub02/", - "dir00/Sub02/file", - "dir00/file", - "dir00/sUB01/", - "dir00/sub00/", - "dir00/sub00/file", - "modified_file", - "new_file", - NULL, - }; - static const char *expect_noauto[] = { - "DIR01/", - "current_file", - "dir00/", - "modified_file", - "new_file", - NULL, - }; - - g_repo = cl_git_sandbox_init("status"); - - build_workdir_tree("status/subdir", 2, 4); - - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", NULL)); - expect_iterator_items(i, 8, expect_base, 8, expect_base); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 18, expect_trees, 18, expect_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); - git_iterator_free(i); - - git__tsort((void **)expect_base, 8, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); - git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 8, expect_base, 8, expect_base); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 18, expect_trees, 18, expect_trees); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_DONT_AUTOEXPAND; - cl_git_pass(git_iterator_for_filesystem(&i, "status/subdir", &i_opts)); - expect_iterator_items(i, 5, expect_noauto, 18, expect_trees); - git_iterator_free(i); -} - -void test_repo_iterator__fs2(void) -{ - git_iterator *i; - static const char *expect_base[] = { - "heads/br2", - "heads/dir", - "heads/ident", - "heads/long-file-name", - "heads/master", - "heads/packed-test", - "heads/subtrees", - "heads/test", - "tags/e90810b", - "tags/foo/bar", - "tags/foo/foo/bar", - "tags/point_to_blob", - "tags/test", - NULL, - }; - - g_repo = cl_git_sandbox_init("testrepo"); - - cl_git_pass(git_iterator_for_filesystem( - &i, "testrepo/.git/refs", NULL)); - expect_iterator_items(i, 13, expect_base, 13, expect_base); - git_iterator_free(i); -} - -void test_repo_iterator__unreadable_dir(void) -{ - git_iterator *i; - const git_index_entry *e; - - if (!cl_is_chmod_supported()) - return; - -#ifndef GIT_WIN32 - if (geteuid() == 0) - cl_skip(); -#endif - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_must_pass(p_mkdir("empty_standard_repo/r", 0777)); - cl_git_mkfile("empty_standard_repo/r/a", "hello"); - cl_must_pass(p_mkdir("empty_standard_repo/r/b", 0777)); - cl_git_mkfile("empty_standard_repo/r/b/problem", "not me"); - cl_must_pass(p_chmod("empty_standard_repo/r/b", 0000)); - cl_must_pass(p_mkdir("empty_standard_repo/r/c", 0777)); - cl_git_mkfile("empty_standard_repo/r/d", "final"); - - cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo/r", NULL)); - - cl_git_pass(git_iterator_advance(&e, i)); /* a */ - cl_git_fail(git_iterator_advance(&e, i)); /* b */ - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); - - cl_must_pass(p_chmod("empty_standard_repo/r/b", 0777)); - - git_iterator_free(i); -} - -void test_repo_iterator__skips_fifos_and_such(void) -{ -#ifndef GIT_WIN32 - git_iterator *i; - const git_index_entry *e; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - - g_repo = cl_git_sandbox_init("empty_standard_repo"); - - cl_must_pass(p_mkdir("empty_standard_repo/dir", 0777)); - cl_git_mkfile("empty_standard_repo/file", "not me"); - - cl_assert(!mkfifo("empty_standard_repo/fifo", 0777)); - cl_assert(!access("empty_standard_repo/fifo", F_OK)); - - i_opts.flags = GIT_ITERATOR_INCLUDE_TREES | - GIT_ITERATOR_DONT_AUTOEXPAND; - - cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo", &i_opts)); - - cl_git_pass(git_iterator_advance(&e, i)); /* .git */ - cl_assert(S_ISDIR(e->mode)); - cl_git_pass(git_iterator_advance(&e, i)); /* dir */ - cl_assert(S_ISDIR(e->mode)); - /* skips fifo */ - cl_git_pass(git_iterator_advance(&e, i)); /* file */ - cl_assert(S_ISREG(e->mode)); - - cl_assert_equal_i(GIT_ITEROVER, git_iterator_advance(&e, i)); - - git_iterator_free(i); -#endif -} - -void test_repo_iterator__indexfilelist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist; - int default_icase; - int expect; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "c"; - i_opts.end = NULL; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ - expect = ((default_icase) ? 6 : 4); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - i_opts.start = NULL; - i_opts.end = "e"; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ - expect = ((default_icase) ? 5 : 6); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_repo_iterator__indexfilelist_2(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a ==> 5) vs (c e k/1 ==> 3) */ - expect = default_icase ? 5 : 3; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_repo_iterator__indexfilelist_3(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ - expect = default_icase ? 8 : 5; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_repo_iterator__indexfilelist_4(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - git_vector filelist = GIT_VECTOR_INIT; - int default_icase, expect; - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "0")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - - /* In this test we DO NOT force a case setting on the index. */ - default_icase = ((git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0); - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "b"; - i_opts.end = "k/D"; - - /* (c D e k/1 k/a k/B k/c k/D) vs (c e k/1 k/B k/D) */ - expect = default_icase ? 8 : 5; - - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_index_free(index); - git_vector_free(&filelist); -} - -void test_repo_iterator__indexfilelist_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_index *index; - int caps; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - cl_git_pass(git_repository_index(&index, g_repo)); - caps = git_index_caps(index); - - /* force case sensitivity */ - cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 1, NULL, 1, NULL); - git_iterator_free(i); - - /* force case insensitivity */ - cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_index(&i, g_repo, index, &i_opts)); - expect_iterator_items(i, 2, NULL, 2, NULL); - git_iterator_free(i); - - cl_git_pass(git_index_set_caps(index, caps)); - git_index_free(index); - git_vector_free(&filelist); -} - -void test_repo_iterator__workdirfilelist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - bool default_icase; - int expect; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - /* In this test we DO NOT force a case on the iteratords and verify default behavior. */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "c"; - i_opts.end = NULL; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ - expect = ((default_icase) ? 6 : 4); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - i_opts.start = NULL; - i_opts.end = "e"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ - expect = ((default_icase) ? 5 : 6); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); -} - -void test_repo_iterator__workdirfilelist_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 1, NULL, 1, NULL); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, &i_opts)); - expect_iterator_items(i, 2, NULL, 2, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); -} - -void test_repo_iterator__treefilelist(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - bool default_icase; - int expect; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - git_repository_head_tree(&tree, g_repo); - - /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - /* In this test we DO NOT force a case on the iteratords and verify default behavior. */ - - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); - git_iterator_free(i); - - i_opts.start = "c"; - i_opts.end = NULL; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (c D e k/1 k/a L ==> 6) vs (c e k/1 k/a ==> 4) */ - expect = ((default_icase) ? 6 : 4); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - i_opts.start = NULL; - i_opts.end = "e"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - default_icase = git_iterator_ignore_case(i); - /* (a B c D e ==> 5) vs (B D L/1 a c e ==> 6) */ - expect = ((default_icase) ? 5 : 6); - expect_iterator_items(i, expect, NULL, expect, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); - git_tree_free(tree); -} - -void test_repo_iterator__treefilelist_icase(void) -{ - git_iterator *i; - git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; - git_vector filelist; - git_tree *tree; - - cl_git_pass(git_vector_init(&filelist, 100, &git__strcmp_cb)); - cl_git_pass(git_vector_insert(&filelist, "a")); - cl_git_pass(git_vector_insert(&filelist, "B")); - cl_git_pass(git_vector_insert(&filelist, "c")); - cl_git_pass(git_vector_insert(&filelist, "D")); - cl_git_pass(git_vector_insert(&filelist, "e")); - cl_git_pass(git_vector_insert(&filelist, "k.a")); - cl_git_pass(git_vector_insert(&filelist, "k.b")); - cl_git_pass(git_vector_insert(&filelist, "k/1")); - cl_git_pass(git_vector_insert(&filelist, "k/a")); - cl_git_pass(git_vector_insert(&filelist, "kZZZZ")); - cl_git_pass(git_vector_insert(&filelist, "L/1")); - - g_repo = cl_git_sandbox_init("icase"); - git_repository_head_tree(&tree, g_repo); - - i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.pathlist.strings = (char **)filelist.contents; - i_opts.pathlist.count = filelist.length; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 3, NULL, 3, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 1, NULL, 1, NULL); - git_iterator_free(i); - - i_opts.flags = GIT_ITERATOR_IGNORE_CASE; - - i_opts.start = "c"; - i_opts.end = "k/D"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 5, NULL, 5, NULL); - git_iterator_free(i); - - i_opts.start = "k"; - i_opts.end = "k/Z"; - cl_git_pass(git_iterator_for_tree(&i, tree, &i_opts)); - expect_iterator_items(i, 2, NULL, 2, NULL); - git_iterator_free(i); - - git_vector_free(&filelist); - git_tree_free(tree); -} diff --git a/tests/repo/open.c b/tests/repo/open.c index 7cdd182a7..6114ad2e1 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -3,6 +3,7 @@ #include "sysdir.h" #include + void test_repo_open__cleanup(void) { cl_git_sandbox_cleanup(); @@ -206,6 +207,12 @@ void test_repo_open__failures(void) cl_git_fail(git_repository_open_ext(&repo, "alternate", 0, NULL)); cl_git_fail(git_repository_open_ext(&repo, "alternate/.git", 0, NULL)); + /* fail with no searching and no appending .git */ + cl_git_fail(git_repository_open_ext( + &repo, "attr", + GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT, + NULL)); + git_buf_free(&ceiling); } @@ -395,3 +402,4 @@ void test_repo_open__force_bare(void) cl_assert(git_repository_is_bare(barerepo)); git_repository_free(barerepo); } + diff --git a/tests/resources/diff_format_email/.gitted/index b/tests/resources/diff_format_email/.gitted/index index d94f87de8..092a888e7 100644 Binary files a/tests/resources/diff_format_email/.gitted/index and b/tests/resources/diff_format_email/.gitted/index differ diff --git a/tests/resources/diff_format_email/.gitted/objects/06/b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6 b/tests/resources/diff_format_email/.gitted/objects/06/b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6 new file mode 100644 index 000000000..37588f169 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/06/b7b69a62cbd1e53c6c4e0c3f16473dcfdb4af6 differ diff --git a/tests/resources/diff_format_email/.gitted/objects/52/19b9784f9a92d7bd7cb567a6d6a21bfb86697e b/tests/resources/diff_format_email/.gitted/objects/52/19b9784f9a92d7bd7cb567a6d6a21bfb86697e new file mode 100644 index 000000000..534e3b07d Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/52/19b9784f9a92d7bd7cb567a6d6a21bfb86697e differ diff --git a/tests/resources/diff_format_email/.gitted/objects/53/525d4cc3ef3ba4a5cbf69492fdffb4e4a74558 b/tests/resources/diff_format_email/.gitted/objects/53/525d4cc3ef3ba4a5cbf69492fdffb4e4a74558 new file mode 100644 index 000000000..b74d31f4f Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/53/525d4cc3ef3ba4a5cbf69492fdffb4e4a74558 differ diff --git a/tests/resources/diff_format_email/.gitted/objects/a7/a65f98355b5a7567bcc395f6f7936c9252cf57 b/tests/resources/diff_format_email/.gitted/objects/a7/a65f98355b5a7567bcc395f6f7936c9252cf57 new file mode 100644 index 000000000..36a4b7be6 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/a7/a65f98355b5a7567bcc395f6f7936c9252cf57 differ diff --git a/tests/resources/diff_format_email/.gitted/objects/c7/1a05d36025c806496a74d46d7d596eb23295c4 b/tests/resources/diff_format_email/.gitted/objects/c7/1a05d36025c806496a74d46d7d596eb23295c4 new file mode 100644 index 000000000..2822fe3e4 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/c7/1a05d36025c806496a74d46d7d596eb23295c4 differ diff --git a/tests/resources/diff_format_email/.gitted/objects/d3/b6b38486f620b5b532a8cc6e0198ab7c3f52e4 b/tests/resources/diff_format_email/.gitted/objects/d3/b6b38486f620b5b532a8cc6e0198ab7c3f52e4 new file mode 100644 index 000000000..5b2e664f4 Binary files /dev/null and b/tests/resources/diff_format_email/.gitted/objects/d3/b6b38486f620b5b532a8cc6e0198ab7c3f52e4 differ diff --git a/tests/resources/diff_format_email/.gitted/refs/heads/master b/tests/resources/diff_format_email/.gitted/refs/heads/master index 3bc734d47..4024b97cd 100644 Binary files a/tests/resources/diff_format_email/.gitted/refs/heads/master and b/tests/resources/diff_format_email/.gitted/refs/heads/master differ diff --git a/tests/resources/diff_format_email/file3.txt b/tests/resources/diff_format_email/file3.txt index 730965344..c71a05d36 100644 Binary files a/tests/resources/diff_format_email/file3.txt and b/tests/resources/diff_format_email/file3.txt differ diff --git a/tests/resources/rebase/.gitted/objects/1d/83f106355e4309a293e42ad2a2c4b8bdbe77ae b/tests/resources/rebase/.gitted/objects/1d/83f106355e4309a293e42ad2a2c4b8bdbe77ae new file mode 100644 index 000000000..6230fdf35 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/1d/83f106355e4309a293e42ad2a2c4b8bdbe77ae differ diff --git a/tests/resources/rebase/.gitted/objects/5a/72bf3bf964fdb176ffa4587312e69e2039695a b/tests/resources/rebase/.gitted/objects/5a/72bf3bf964fdb176ffa4587312e69e2039695a new file mode 100644 index 000000000..80eb921d6 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/5a/72bf3bf964fdb176ffa4587312e69e2039695a differ diff --git a/tests/resources/rebase/.gitted/objects/77/0f14546ee2563a26c52afa5cc4139a96e5d360 b/tests/resources/rebase/.gitted/objects/77/0f14546ee2563a26c52afa5cc4139a96e5d360 new file mode 100644 index 000000000..6091d54a0 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/77/0f14546ee2563a26c52afa5cc4139a96e5d360 differ diff --git a/tests/resources/rebase/.gitted/objects/91/4f3c604d1098847b7fe275f659ee329878153f b/tests/resources/rebase/.gitted/objects/91/4f3c604d1098847b7fe275f659ee329878153f new file mode 100644 index 000000000..726bf9177 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/91/4f3c604d1098847b7fe275f659ee329878153f differ diff --git a/tests/resources/rebase/.gitted/objects/d9/c5185186d95d233dc007c1927cb3bdd6cde35b b/tests/resources/rebase/.gitted/objects/d9/c5185186d95d233dc007c1927cb3bdd6cde35b new file mode 100644 index 000000000..3c340db84 Binary files /dev/null and b/tests/resources/rebase/.gitted/objects/d9/c5185186d95d233dc007c1927cb3bdd6cde35b differ diff --git a/tests/resources/rebase/.gitted/refs/heads/deep_gravy b/tests/resources/rebase/.gitted/refs/heads/deep_gravy new file mode 100644 index 000000000..efbe5f0a0 Binary files /dev/null and b/tests/resources/rebase/.gitted/refs/heads/deep_gravy differ diff --git a/tests/resources/submod3/.gitted/HEAD b/tests/resources/submod3/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/HEAD differ diff --git a/tests/resources/submod3/.gitted/config b/tests/resources/submod3/.gitted/config new file mode 100644 index 000000000..51fbbe981 Binary files /dev/null and b/tests/resources/submod3/.gitted/config differ diff --git a/tests/resources/submod3/.gitted/index b/tests/resources/submod3/.gitted/index new file mode 100644 index 000000000..fc688e264 Binary files /dev/null and b/tests/resources/submod3/.gitted/index differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/HEAD b/tests/resources/submod3/.gitted/modules/EIGHT/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/config b/tests/resources/submod3/.gitted/modules/EIGHT/config new file mode 100755 index 000000000..c95068ad7 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/config differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/index b/tests/resources/submod3/.gitted/modules/EIGHT/index new file mode 100644 index 000000000..4217b3fb9 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/index differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/EIGHT/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/EIGHT/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/EIGHT/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/EIGHT/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/EIGHT/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/packed-refs b/tests/resources/submod3/.gitted/modules/EIGHT/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/refs/heads/master b/tests/resources/submod3/.gitted/modules/EIGHT/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/EIGHT/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/EIGHT/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/EIGHT/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/Five/HEAD b/tests/resources/submod3/.gitted/modules/Five/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/Five/config b/tests/resources/submod3/.gitted/modules/Five/config new file mode 100755 index 000000000..18259e810 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/config differ diff --git a/tests/resources/submod3/.gitted/modules/Five/index b/tests/resources/submod3/.gitted/modules/Five/index new file mode 100644 index 000000000..bd356c824 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/index differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/Five/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/Five/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/Five/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/Five/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/Five/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/Five/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/Five/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/Five/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/Five/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/Five/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/Five/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/Five/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/Five/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/Five/packed-refs b/tests/resources/submod3/.gitted/modules/Five/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/Five/refs/heads/master b/tests/resources/submod3/.gitted/modules/Five/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/Five/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/Five/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/Five/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/HEAD b/tests/resources/submod3/.gitted/modules/FoUr/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/config b/tests/resources/submod3/.gitted/modules/FoUr/config new file mode 100755 index 000000000..8e41a32a7 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/config differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/index b/tests/resources/submod3/.gitted/modules/FoUr/index new file mode 100644 index 000000000..bfc441bb3 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/index differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/FoUr/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/FoUr/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/FoUr/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/FoUr/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/FoUr/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/FoUr/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/FoUr/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/FoUr/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/FoUr/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/FoUr/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/FoUr/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/FoUr/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/packed-refs b/tests/resources/submod3/.gitted/modules/FoUr/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/refs/heads/master b/tests/resources/submod3/.gitted/modules/FoUr/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/FoUr/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/FoUr/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/FoUr/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/One/HEAD b/tests/resources/submod3/.gitted/modules/One/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/One/config b/tests/resources/submod3/.gitted/modules/One/config new file mode 100755 index 000000000..303b86d4f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/config differ diff --git a/tests/resources/submod3/.gitted/modules/One/index b/tests/resources/submod3/.gitted/modules/One/index new file mode 100644 index 000000000..2fa766b9d Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/index differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/One/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/One/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/One/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/One/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/One/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/One/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/One/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/One/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/One/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/One/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/One/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/One/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/One/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/One/packed-refs b/tests/resources/submod3/.gitted/modules/One/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/One/refs/heads/master b/tests/resources/submod3/.gitted/modules/One/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/One/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/One/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/One/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/HEAD b/tests/resources/submod3/.gitted/modules/TEN/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/config b/tests/resources/submod3/.gitted/modules/TEN/config new file mode 100755 index 000000000..0c8328835 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/config differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/index b/tests/resources/submod3/.gitted/modules/TEN/index new file mode 100644 index 000000000..202f0e27d Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/index differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/TEN/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/TEN/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/TEN/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/TEN/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/TEN/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/TEN/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/TEN/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/TEN/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/TEN/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/TEN/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/TEN/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/TEN/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/packed-refs b/tests/resources/submod3/.gitted/modules/TEN/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/refs/heads/master b/tests/resources/submod3/.gitted/modules/TEN/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/TEN/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/TEN/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TEN/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/HEAD b/tests/resources/submod3/.gitted/modules/TWO/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/config b/tests/resources/submod3/.gitted/modules/TWO/config new file mode 100755 index 000000000..4d9022262 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/config differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/index b/tests/resources/submod3/.gitted/modules/TWO/index new file mode 100644 index 000000000..42053137a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/index differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/TWO/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/TWO/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/TWO/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/TWO/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/TWO/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/TWO/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/TWO/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/TWO/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/TWO/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/TWO/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/TWO/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/TWO/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/packed-refs b/tests/resources/submod3/.gitted/modules/TWO/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/refs/heads/master b/tests/resources/submod3/.gitted/modules/TWO/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/TWO/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/TWO/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/TWO/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/nine/HEAD b/tests/resources/submod3/.gitted/modules/nine/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/nine/config b/tests/resources/submod3/.gitted/modules/nine/config new file mode 100755 index 000000000..7c73f8a6d Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/config differ diff --git a/tests/resources/submod3/.gitted/modules/nine/index b/tests/resources/submod3/.gitted/modules/nine/index new file mode 100644 index 000000000..48e3e9e07 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/index differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/nine/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/nine/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/nine/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/nine/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/nine/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/nine/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/nine/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/nine/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/nine/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/nine/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/nine/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/nine/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/nine/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/nine/packed-refs b/tests/resources/submod3/.gitted/modules/nine/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/nine/refs/heads/master b/tests/resources/submod3/.gitted/modules/nine/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/nine/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/nine/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/nine/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/HEAD b/tests/resources/submod3/.gitted/modules/sEvEn/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/config b/tests/resources/submod3/.gitted/modules/sEvEn/config new file mode 100755 index 000000000..1ef3be7d5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/config differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/index b/tests/resources/submod3/.gitted/modules/sEvEn/index new file mode 100644 index 000000000..0a3a842e3 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/index differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/sEvEn/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/sEvEn/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/sEvEn/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/sEvEn/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/sEvEn/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/packed-refs b/tests/resources/submod3/.gitted/modules/sEvEn/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/refs/heads/master b/tests/resources/submod3/.gitted/modules/sEvEn/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/sEvEn/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/sEvEn/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/sEvEn/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/six/HEAD b/tests/resources/submod3/.gitted/modules/six/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/six/config b/tests/resources/submod3/.gitted/modules/six/config new file mode 100755 index 000000000..4296e81a4 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/config differ diff --git a/tests/resources/submod3/.gitted/modules/six/index b/tests/resources/submod3/.gitted/modules/six/index new file mode 100644 index 000000000..fbd09fde6 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/index differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/six/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/six/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/six/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/six/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/six/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/six/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/six/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/six/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/six/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/six/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/six/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/six/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/six/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/six/packed-refs b/tests/resources/submod3/.gitted/modules/six/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/six/refs/heads/master b/tests/resources/submod3/.gitted/modules/six/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/six/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/six/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/six/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/three/HEAD b/tests/resources/submod3/.gitted/modules/three/HEAD new file mode 100644 index 000000000..cb089cd89 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/HEAD differ diff --git a/tests/resources/submod3/.gitted/modules/three/config b/tests/resources/submod3/.gitted/modules/three/config new file mode 100755 index 000000000..ba5c9663e Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/config differ diff --git a/tests/resources/submod3/.gitted/modules/three/index b/tests/resources/submod3/.gitted/modules/three/index new file mode 100644 index 000000000..ec4a944e0 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/index differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/tests/resources/submod3/.gitted/modules/three/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/tests/resources/submod3/.gitted/modules/three/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/tests/resources/submod3/.gitted/modules/three/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/tests/resources/submod3/.gitted/modules/three/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/tests/resources/submod3/.gitted/modules/three/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/5e/4963595a9774b90524d35a807169049de8ccad b/tests/resources/submod3/.gitted/modules/three/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/tests/resources/submod3/.gitted/modules/three/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/6b/31c659545507c381e9cd34ec508f16c04e149e differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/73/ba924a80437097795ae839e66e187c55d3babf b/tests/resources/submod3/.gitted/modules/three/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/tests/resources/submod3/.gitted/modules/three/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/tests/resources/submod3/.gitted/modules/three/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/78/9efbdadaa4a582778d4584385495559ea0994b differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/tests/resources/submod3/.gitted/modules/three/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/tests/resources/submod3/.gitted/modules/three/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/tests/resources/submod3/.gitted/modules/three/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/tests/resources/submod3/.gitted/modules/three/packed-refs b/tests/resources/submod3/.gitted/modules/three/packed-refs new file mode 100644 index 000000000..def303a5f Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/packed-refs differ diff --git a/tests/resources/submod3/.gitted/modules/three/refs/heads/master b/tests/resources/submod3/.gitted/modules/three/refs/heads/master new file mode 100644 index 000000000..e12c44d7a Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/refs/heads/master differ diff --git a/tests/resources/submod3/.gitted/modules/three/refs/remotes/origin/HEAD b/tests/resources/submod3/.gitted/modules/three/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff Binary files /dev/null and b/tests/resources/submod3/.gitted/modules/three/refs/remotes/origin/HEAD differ diff --git a/tests/resources/submod3/.gitted/objects/0d/db6db2835a283823cd700a1d18f17b0ba6520d b/tests/resources/submod3/.gitted/objects/0d/db6db2835a283823cd700a1d18f17b0ba6520d new file mode 100644 index 000000000..8f1c5ca31 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/0d/db6db2835a283823cd700a1d18f17b0ba6520d differ diff --git a/tests/resources/submod3/.gitted/objects/0e/db4f085060fea212aecc25242d4c7685cdc01d b/tests/resources/submod3/.gitted/objects/0e/db4f085060fea212aecc25242d4c7685cdc01d new file mode 100644 index 000000000..34179a45e Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/0e/db4f085060fea212aecc25242d4c7685cdc01d differ diff --git a/tests/resources/submod3/.gitted/objects/33/61720a7115648e518342a6524b29cc627ea11a b/tests/resources/submod3/.gitted/objects/33/61720a7115648e518342a6524b29cc627ea11a new file mode 100644 index 000000000..c09eabcc6 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/33/61720a7115648e518342a6524b29cc627ea11a differ diff --git a/tests/resources/submod3/.gitted/objects/4f/6ea8092cb19f39e25cd1b21c061893b6ce17bd b/tests/resources/submod3/.gitted/objects/4f/6ea8092cb19f39e25cd1b21c061893b6ce17bd new file mode 100644 index 000000000..cf653933a Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/4f/6ea8092cb19f39e25cd1b21c061893b6ce17bd differ diff --git a/tests/resources/submod3/.gitted/objects/5b/6f93e4846d705ee2daa5d60348e7fc1c6715ed b/tests/resources/submod3/.gitted/objects/5b/6f93e4846d705ee2daa5d60348e7fc1c6715ed new file mode 100644 index 000000000..591d4b804 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/5b/6f93e4846d705ee2daa5d60348e7fc1c6715ed differ diff --git a/tests/resources/submod3/.gitted/objects/6e/7201a58feeaa462ac9f545928fe0b961ad9495 b/tests/resources/submod3/.gitted/objects/6e/7201a58feeaa462ac9f545928fe0b961ad9495 new file mode 100644 index 000000000..a5dc38e35 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/6e/7201a58feeaa462ac9f545928fe0b961ad9495 differ diff --git a/tests/resources/submod3/.gitted/objects/6f/b39bdc90378a0a9a05a127da035e560ced3900 b/tests/resources/submod3/.gitted/objects/6f/b39bdc90378a0a9a05a127da035e560ced3900 new file mode 100644 index 000000000..315e3b831 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/6f/b39bdc90378a0a9a05a127da035e560ced3900 differ diff --git a/tests/resources/submod3/.gitted/objects/90/459b51713bde15eb97852ff22c29270752b432 b/tests/resources/submod3/.gitted/objects/90/459b51713bde15eb97852ff22c29270752b432 new file mode 100644 index 000000000..3a2c99cc7 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/90/459b51713bde15eb97852ff22c29270752b432 differ diff --git a/tests/resources/submod3/.gitted/objects/d0/ea28557a5f50013ac72938bc285c2d8572e50d b/tests/resources/submod3/.gitted/objects/d0/ea28557a5f50013ac72938bc285c2d8572e50d new file mode 100644 index 000000000..9b45f5cbd Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/d0/ea28557a5f50013ac72938bc285c2d8572e50d differ diff --git a/tests/resources/submod3/.gitted/objects/d0/fe2af38ea8925d5b4982b56354ca17816b7e11 b/tests/resources/submod3/.gitted/objects/d0/fe2af38ea8925d5b4982b56354ca17816b7e11 new file mode 100644 index 000000000..4907b202a Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/d0/fe2af38ea8925d5b4982b56354ca17816b7e11 differ diff --git a/tests/resources/submod3/.gitted/objects/e6/4b5c9e517bbb34962611400cde683690e56aa8 b/tests/resources/submod3/.gitted/objects/e6/4b5c9e517bbb34962611400cde683690e56aa8 new file mode 100644 index 000000000..701584771 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/e6/4b5c9e517bbb34962611400cde683690e56aa8 differ diff --git a/tests/resources/submod3/.gitted/objects/e7/b6f5010b47e84573eb670d8b31f19fccab6964 b/tests/resources/submod3/.gitted/objects/e7/b6f5010b47e84573eb670d8b31f19fccab6964 new file mode 100644 index 000000000..4b93a3715 Binary files /dev/null and b/tests/resources/submod3/.gitted/objects/e7/b6f5010b47e84573eb670d8b31f19fccab6964 differ diff --git a/tests/resources/submod3/.gitted/refs/heads/master b/tests/resources/submod3/.gitted/refs/heads/master new file mode 100644 index 000000000..706cfc46c Binary files /dev/null and b/tests/resources/submod3/.gitted/refs/heads/master differ diff --git a/tests/resources/submod3/EIGHT/.gitted b/tests/resources/submod3/EIGHT/.gitted new file mode 100644 index 000000000..85c342420 Binary files /dev/null and b/tests/resources/submod3/EIGHT/.gitted differ diff --git a/tests/resources/submod3/EIGHT/README.txt b/tests/resources/submod3/EIGHT/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/EIGHT/README.txt differ diff --git a/tests/resources/submod3/EIGHT/file_to_modify b/tests/resources/submod3/EIGHT/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/EIGHT/file_to_modify differ diff --git a/tests/resources/submod3/Five/.gitted b/tests/resources/submod3/Five/.gitted new file mode 100644 index 000000000..6a743bbdc Binary files /dev/null and b/tests/resources/submod3/Five/.gitted differ diff --git a/tests/resources/submod3/Five/README.txt b/tests/resources/submod3/Five/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/Five/README.txt differ diff --git a/tests/resources/submod3/Five/file_to_modify b/tests/resources/submod3/Five/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/Five/file_to_modify differ diff --git a/tests/resources/submod3/FoUr/.gitted b/tests/resources/submod3/FoUr/.gitted new file mode 100644 index 000000000..2cef33432 Binary files /dev/null and b/tests/resources/submod3/FoUr/.gitted differ diff --git a/tests/resources/submod3/FoUr/README.txt b/tests/resources/submod3/FoUr/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/FoUr/README.txt differ diff --git a/tests/resources/submod3/FoUr/file_to_modify b/tests/resources/submod3/FoUr/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/FoUr/file_to_modify differ diff --git a/tests/resources/submod3/One/.gitted b/tests/resources/submod3/One/.gitted new file mode 100644 index 000000000..85428882e Binary files /dev/null and b/tests/resources/submod3/One/.gitted differ diff --git a/tests/resources/submod3/One/README.txt b/tests/resources/submod3/One/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/One/README.txt differ diff --git a/tests/resources/submod3/One/file_to_modify b/tests/resources/submod3/One/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/One/file_to_modify differ diff --git a/tests/resources/submod3/TEN/.gitted b/tests/resources/submod3/TEN/.gitted new file mode 100644 index 000000000..f8f7b838b Binary files /dev/null and b/tests/resources/submod3/TEN/.gitted differ diff --git a/tests/resources/submod3/TEN/README.txt b/tests/resources/submod3/TEN/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/TEN/README.txt differ diff --git a/tests/resources/submod3/TEN/file_to_modify b/tests/resources/submod3/TEN/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/TEN/file_to_modify differ diff --git a/tests/resources/submod3/TWO/.gitted b/tests/resources/submod3/TWO/.gitted new file mode 100644 index 000000000..e1ddbd741 Binary files /dev/null and b/tests/resources/submod3/TWO/.gitted differ diff --git a/tests/resources/submod3/TWO/README.txt b/tests/resources/submod3/TWO/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/TWO/README.txt differ diff --git a/tests/resources/submod3/TWO/file_to_modify b/tests/resources/submod3/TWO/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/TWO/file_to_modify differ diff --git a/tests/resources/submod3/gitmodules b/tests/resources/submod3/gitmodules new file mode 100644 index 000000000..7308ab0fb Binary files /dev/null and b/tests/resources/submod3/gitmodules differ diff --git a/tests/resources/submod3/nine/.gitted b/tests/resources/submod3/nine/.gitted new file mode 100644 index 000000000..a49dfa774 Binary files /dev/null and b/tests/resources/submod3/nine/.gitted differ diff --git a/tests/resources/submod3/nine/README.txt b/tests/resources/submod3/nine/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/nine/README.txt differ diff --git a/tests/resources/submod3/nine/file_to_modify b/tests/resources/submod3/nine/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/nine/file_to_modify differ diff --git a/tests/resources/submod3/sEvEn/.gitted b/tests/resources/submod3/sEvEn/.gitted new file mode 100644 index 000000000..20e83f05c Binary files /dev/null and b/tests/resources/submod3/sEvEn/.gitted differ diff --git a/tests/resources/submod3/sEvEn/README.txt b/tests/resources/submod3/sEvEn/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/sEvEn/README.txt differ diff --git a/tests/resources/submod3/sEvEn/file_to_modify b/tests/resources/submod3/sEvEn/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/sEvEn/file_to_modify differ diff --git a/tests/resources/submod3/six/.gitted b/tests/resources/submod3/six/.gitted new file mode 100644 index 000000000..4dec8956f Binary files /dev/null and b/tests/resources/submod3/six/.gitted differ diff --git a/tests/resources/submod3/six/README.txt b/tests/resources/submod3/six/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/six/README.txt differ diff --git a/tests/resources/submod3/six/file_to_modify b/tests/resources/submod3/six/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/six/file_to_modify differ diff --git a/tests/resources/submod3/three/.gitted b/tests/resources/submod3/three/.gitted new file mode 100644 index 000000000..6025d2d35 Binary files /dev/null and b/tests/resources/submod3/three/.gitted differ diff --git a/tests/resources/submod3/three/README.txt b/tests/resources/submod3/three/README.txt new file mode 100644 index 000000000..780d7397f Binary files /dev/null and b/tests/resources/submod3/three/README.txt differ diff --git a/tests/resources/submod3/three/file_to_modify b/tests/resources/submod3/three/file_to_modify new file mode 100644 index 000000000..789efbdad Binary files /dev/null and b/tests/resources/submod3/three/file_to_modify differ diff --git a/tests/resources/testrepo.git/objects/43/da5ec3274dd061df152ff5e69853d562b01842 b/tests/resources/testrepo.git/objects/43/da5ec3274dd061df152ff5e69853d562b01842 new file mode 100644 index 000000000..298feece4 Binary files /dev/null and b/tests/resources/testrepo.git/objects/43/da5ec3274dd061df152ff5e69853d562b01842 differ diff --git a/tests/resources/testrepo.git/objects/43/e968a905a821532069bb413801d35b200631cf b/tests/resources/testrepo.git/objects/43/e968a905a821532069bb413801d35b200631cf new file mode 100644 index 000000000..ec04abf68 Binary files /dev/null and b/tests/resources/testrepo.git/objects/43/e968a905a821532069bb413801d35b200631cf differ diff --git a/tests/resources/testrepo.git/objects/5d/0f8f7891e872d284beef38254882dc879b2602 b/tests/resources/testrepo.git/objects/5d/0f8f7891e872d284beef38254882dc879b2602 new file mode 100644 index 000000000..7a22451ed Binary files /dev/null and b/tests/resources/testrepo.git/objects/5d/0f8f7891e872d284beef38254882dc879b2602 differ diff --git a/tests/resources/testrepo.git/objects/5f/34cd6e3285089647165983482cf90873d50940 b/tests/resources/testrepo.git/objects/5f/34cd6e3285089647165983482cf90873d50940 new file mode 100644 index 000000000..b1df3bdd5 Binary files /dev/null and b/tests/resources/testrepo.git/objects/5f/34cd6e3285089647165983482cf90873d50940 differ diff --git a/tests/resources/testrepo.git/objects/8e/73b769e97678d684b809b163bebdae2911720f b/tests/resources/testrepo.git/objects/8e/73b769e97678d684b809b163bebdae2911720f new file mode 100644 index 000000000..d75977a25 Binary files /dev/null and b/tests/resources/testrepo.git/objects/8e/73b769e97678d684b809b163bebdae2911720f differ diff --git a/tests/resources/testrepo.git/objects/b2/04707bbc546a1a770ef6ced37c7089cc3bfe6b b/tests/resources/testrepo.git/objects/b2/04707bbc546a1a770ef6ced37c7089cc3bfe6b new file mode 100644 index 000000000..f9ec61c1e Binary files /dev/null and b/tests/resources/testrepo.git/objects/b2/04707bbc546a1a770ef6ced37c7089cc3bfe6b differ diff --git a/tests/resources/testrepo.git/objects/b2/35959d89084af8d3544fbdf675e47944f86524 b/tests/resources/testrepo.git/objects/b2/35959d89084af8d3544fbdf675e47944f86524 new file mode 100644 index 000000000..7d563dbd3 Binary files /dev/null and b/tests/resources/testrepo.git/objects/b2/35959d89084af8d3544fbdf675e47944f86524 differ diff --git a/tests/resources/testrepo.git/objects/b9/1e763008b10db366442469339f90a2b8400d0a b/tests/resources/testrepo.git/objects/b9/1e763008b10db366442469339f90a2b8400d0a new file mode 100644 index 000000000..7bab59be8 Binary files /dev/null and b/tests/resources/testrepo.git/objects/b9/1e763008b10db366442469339f90a2b8400d0a differ diff --git a/tests/resources/testrepo.git/objects/bd/758010071961f28336333bc41e9c64c9a64866 b/tests/resources/testrepo.git/objects/bd/758010071961f28336333bc41e9c64c9a64866 new file mode 100644 index 000000000..c5e3b87ad Binary files /dev/null and b/tests/resources/testrepo.git/objects/bd/758010071961f28336333bc41e9c64c9a64866 differ diff --git a/tests/resources/testrepo.git/objects/db/4df74a2fc340a0d0cb0cafc0db471fdfff1048 b/tests/resources/testrepo.git/objects/db/4df74a2fc340a0d0cb0cafc0db471fdfff1048 new file mode 100644 index 000000000..5f3d50efa Binary files /dev/null and b/tests/resources/testrepo.git/objects/db/4df74a2fc340a0d0cb0cafc0db471fdfff1048 differ diff --git a/tests/resources/testrepo.git/objects/db/793a00a5615eca1aac97e42b3a68b1acfa8bfd b/tests/resources/testrepo.git/objects/db/793a00a5615eca1aac97e42b3a68b1acfa8bfd new file mode 100644 index 000000000..ae82880de Binary files /dev/null and b/tests/resources/testrepo.git/objects/db/793a00a5615eca1aac97e42b3a68b1acfa8bfd differ diff --git a/tests/resources/testrepo.git/objects/db/c0be625bed24b5d8f5d9a927484f2065d321af b/tests/resources/testrepo.git/objects/db/c0be625bed24b5d8f5d9a927484f2065d321af new file mode 100644 index 000000000..b966b0b2f Binary files /dev/null and b/tests/resources/testrepo.git/objects/db/c0be625bed24b5d8f5d9a927484f2065d321af differ diff --git a/tests/resources/testrepo.git/objects/f0/a2a10243ca64f935dbe3dccb89ec8bf16bdace b/tests/resources/testrepo.git/objects/f0/a2a10243ca64f935dbe3dccb89ec8bf16bdace new file mode 100644 index 000000000..1b299dc25 Binary files /dev/null and b/tests/resources/testrepo.git/objects/f0/a2a10243ca64f935dbe3dccb89ec8bf16bdace differ diff --git a/tests/revwalk/basic.c b/tests/revwalk/basic.c index 5ed7da4eb..89140bc54 100644 --- a/tests/revwalk/basic.c +++ b/tests/revwalk/basic.c @@ -38,8 +38,9 @@ static const int commit_sorting_time_reverse[][6] = { {4, 5, 2, 1, 3, 0} }; +/* This is specified unsorted, so both combinations are possible */ static const int commit_sorting_segment[][6] = { - {1, 2, -1, -1, -1, -1} + {1, 2, -1, -1, -1, -1}, {2, 1, -1, -1, -1, -1} }; #define commit_count 6 @@ -155,9 +156,8 @@ void test_revwalk_basic__glob_heads(void) cl_git_pass(git_revwalk_push_glob(_walk, "heads")); - while (git_revwalk_next(&oid, _walk) == 0) { + while (git_revwalk_next(&oid, _walk) == 0) i++; - } /* git log --branches --oneline | wc -l => 14 */ cl_assert_equal_i(i, 14); @@ -338,7 +338,7 @@ void test_revwalk_basic__push_range(void) git_revwalk_reset(_walk); git_revwalk_sorting(_walk, 0); cl_git_pass(git_revwalk_push_range(_walk, "9fd738e~2..9fd738e")); - cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 1)); + cl_git_pass(test_walk_only(_walk, commit_sorting_segment, 2)); } void test_revwalk_basic__push_mixed(void) @@ -473,3 +473,51 @@ void test_revwalk_basic__big_timestamp(void) git_signature_free(sig); } + +/* Ensure that we correctly hide a commit that is (timewise) older + * than the commits that we are showing. + * + * % git rev-list 8e73b76..bd75801 + * bd758010071961f28336333bc41e9c64c9a64866 + */ +void test_revwalk_basic__old_hidden_commit_one(void) +{ + git_oid new_id, old_id, oid; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); + cl_git_pass(git_revwalk_push(_walk, &new_id)); + + cl_git_pass(git_oid_fromstr(&old_id, "8e73b769e97678d684b809b163bebdae2911720f")); + cl_git_pass(git_revwalk_hide(_walk, &old_id)); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); + + cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); +} + +/* Ensure that we correctly hide a commit that is (timewise) older + * than the commits that we are showing. + * + * % git rev-list bd75801 ^b91e763 + * bd758010071961f28336333bc41e9c64c9a64866 + */ +void test_revwalk_basic__old_hidden_commit_two(void) +{ + git_oid new_id, old_id, oid; + + revwalk_basic_setup_walk("testrepo.git"); + + cl_git_pass(git_oid_fromstr(&new_id, "bd758010071961f28336333bc41e9c64c9a64866")); + cl_git_pass(git_revwalk_push(_walk, &new_id)); + + cl_git_pass(git_oid_fromstr(&old_id, "b91e763008b10db366442469339f90a2b8400d0a")); + cl_git_pass(git_revwalk_hide(_walk, &old_id)); + + cl_git_pass(git_revwalk_next(&oid, _walk)); + cl_assert(!git_oid_streq(&oid, "bd758010071961f28336333bc41e9c64c9a64866")); + + cl_git_fail_with(GIT_ITEROVER, git_revwalk_next(&oid, _walk)); +} diff --git a/tests/revwalk/hidecb.c b/tests/revwalk/hidecb.c index 14cf39afd..b274ed86a 100644 --- a/tests/revwalk/hidecb.c +++ b/tests/revwalk/hidecb.c @@ -158,6 +158,7 @@ void test_revwalk_hidecb__hide_some_commits(void) cl_git_pass(git_revwalk_new(&walk, _repo)); cl_git_pass(git_revwalk_push(walk, &_head_id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); /* Add hide callback */ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_cb, NULL)); @@ -182,6 +183,7 @@ void test_revwalk_hidecb__test_payload(void) cl_git_pass(git_revwalk_new(&walk, _repo)); cl_git_pass(git_revwalk_push(walk, &_head_id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); /* Add hide callback, pass id of parent of initial commit as payload data */ cl_git_pass(git_revwalk_add_hide_cb(walk, hide_commit_use_payload_cb, &commit_ids[5])); diff --git a/tests/revwalk/simplify.c b/tests/revwalk/simplify.c index f65ce6c59..6dd068a42 100644 --- a/tests/revwalk/simplify.c +++ b/tests/revwalk/simplify.c @@ -40,6 +40,7 @@ void test_revwalk_simplify__first_parent(void) git_oid_fromstr(&id, commit_head); cl_git_pass(git_revwalk_push(walk, &id)); + git_revwalk_sorting(walk, GIT_SORT_TOPOLOGICAL); git_revwalk_simplify_first_parent(walk); i = 0; diff --git a/tests/status/submodules.c b/tests/status/submodules.c index e6de60088..33c9e5ab4 100644 --- a/tests/status/submodules.c +++ b/tests/status/submodules.c @@ -524,3 +524,40 @@ void test_status_submodules__entry_but_dir_tracked(void) git_tree_free(tree); git_repository_free(repo); } + +void test_status_submodules__mixed_case(void) +{ + git_status_list *status; + git_status_options status_opts = GIT_STATUS_OPTIONS_INIT; + const git_status_entry *s; + size_t i; + + status_opts.flags = + GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | + GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR | + GIT_STATUS_OPT_RENAMES_FROM_REWRITES | + GIT_STATUS_OPT_INCLUDE_UNREADABLE | + GIT_STATUS_OPT_INCLUDE_UNREADABLE_AS_UNTRACKED; + + g_repo = setup_fixture_submod3(); + + cl_git_pass(git_status_list_new(&status, g_repo, &status_opts)); + + for (i = 0; i < git_status_list_entrycount(status); i++) { + s = git_status_byindex(status, i); + + if (s->head_to_index && + strcmp(s->head_to_index->old_file.path, ".gitmodules") == 0) + continue; + + cl_assert_equal_i(0, s->status); + } + + git_status_list_free(status); +} + diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 5d3b4d55e..1345dbfd2 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -218,6 +218,58 @@ void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void) cl_assert_equal_i(0, counts.wrong_sorted_path); } +static void stage_and_commit(git_repository *repo, const char *path) +{ + git_index *index; + + cl_git_pass(git_repository_index(&index, repo)); + cl_git_pass(git_index_add_bypath(index, path)); + cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n"); + git_index_free(index); +} + +void test_status_worktree__within_subdir(void) +{ + status_entry_counts counts; + git_repository *repo = cl_git_sandbox_init("status"); + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + char *paths[] = { "zzz_new_dir" }; + git_strarray pathsArray; + + /* first alter the contents of the worktree */ + cl_git_mkfile("status/.new_file", "dummy"); + cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", 0777)); + cl_git_mkfile("status/zzz_new_dir/new_file", "dummy"); + cl_git_mkfile("status/zzz_new_file", "dummy"); + cl_git_mkfile("status/wut", "dummy"); + + stage_and_commit(repo, "zzz_new_dir/new_file"); + + /* now get status */ + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = entry_count4; + counts.expected_paths = entry_paths4; + counts.expected_statuses = entry_statuses4; + counts.debug = true; + + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + + pathsArray.count = 1; + pathsArray.strings = paths; + opts.pathspec = pathsArray; + + // We committed zzz_new_dir/new_file above. It shouldn't be reported. + cl_git_pass( + git_status_foreach_ext(repo, &opts, cb_status__normal, &counts) + ); + + cl_assert_equal_i(0, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); +} + /* this test is equivalent to t18-status.c:singlestatus0 */ void test_status_worktree__single_file(void) { @@ -692,16 +744,6 @@ void test_status_worktree__conflict_has_no_oid(void) git_status_list_free(statuslist); } -static void stage_and_commit(git_repository *repo, const char *path) -{ - git_index *index; - - cl_git_pass(git_repository_index(&index, repo)); - cl_git_pass(git_index_add_bypath(index, path)); - cl_repo_commit_from_index(NULL, repo, NULL, 1323847743, "Initial commit\n"); - git_index_free(index); -} - static void assert_ignore_case( bool should_ignore_case, int expected_lower_cased_file_status, @@ -1149,3 +1191,92 @@ void test_status_worktree__update_index_with_symlink_doesnt_change_mode(void) git_reference_free(head); } +static const char *testrepo2_subdir_paths[] = { + "subdir/README", + "subdir/new.txt", + "subdir/subdir2/README", + "subdir/subdir2/new.txt", +}; + +static const char *testrepo2_subdir_paths_icase[] = { + "subdir/new.txt", + "subdir/README", + "subdir/subdir2/new.txt", + "subdir/subdir2/README" +}; + +void test_status_worktree__with_directory_in_pathlist(void) +{ + git_repository *repo = cl_git_sandbox_init("testrepo2"); + git_index *index; + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *statuslist; + const git_status_entry *status; + size_t i, entrycount; + bool native_ignore_case; + char *subdir_path = "subdir"; + + cl_git_pass(git_repository_index(&index, repo)); + native_ignore_case = + (git_index_caps(index) & GIT_INDEXCAP_IGNORE_CASE) != 0; + git_index_free(index); + + opts.pathspec.strings = &subdir_path; + opts.pathspec.count = 1; + opts.flags = + GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_UNMODIFIED | + GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH; + + opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->index_to_workdir->old_file.path); + } + + git_status_list_free(statuslist); + + opts.show = GIT_STATUS_SHOW_INDEX_ONLY; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->head_to_index->old_file.path); + } + + git_status_list_free(statuslist); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + git_status_list_new(&statuslist, repo, &opts); + + entrycount = git_status_list_entrycount(statuslist); + cl_assert_equal_i(4, entrycount); + + for (i = 0; i < entrycount; i++) { + status = git_status_byindex(statuslist, i); + cl_assert_equal_i(0, status->status); + cl_assert_equal_s(native_ignore_case ? + testrepo2_subdir_paths_icase[i] : + testrepo2_subdir_paths[i], + status->index_to_workdir->old_file.path); + } + + git_status_list_free(statuslist); +} + diff --git a/tests/submodule/submodule_helpers.c b/tests/submodule/submodule_helpers.c index 4ff4b4da7..6c2b9cf78 100644 --- a/tests/submodule/submodule_helpers.c +++ b/tests/submodule/submodule_helpers.c @@ -126,6 +126,32 @@ git_repository *setup_fixture_submod2(void) return repo; } +git_repository *setup_fixture_submod3(void) +{ + git_repository *repo = cl_git_sandbox_init("submod3"); + + cl_fixture_sandbox("submod2_target"); + p_rename("submod2_target/.gitted", "submod2_target/.git"); + + rewrite_gitmodules(git_repository_workdir(repo)); + p_rename("submod3/One/.gitted", "submod3/One/.git"); + p_rename("submod3/TWO/.gitted", "submod3/TWO/.git"); + p_rename("submod3/three/.gitted", "submod3/three/.git"); + p_rename("submod3/FoUr/.gitted", "submod3/FoUr/.git"); + p_rename("submod3/Five/.gitted", "submod3/Five/.git"); + p_rename("submod3/six/.gitted", "submod3/six/.git"); + p_rename("submod3/sEvEn/.gitted", "submod3/sEvEn/.git"); + p_rename("submod3/EIGHT/.gitted", "submod3/EIGHT/.git"); + p_rename("submod3/nine/.gitted", "submod3/nine/.git"); + p_rename("submod3/TEN/.gitted", "submod3/TEN/.git"); + + cl_set_cleanup(cleanup_fixture_submodules, "submod2_target"); + + cl_git_pass(git_repository_reinit_filesystem(repo, 1)); + + return repo; +} + git_repository *setup_fixture_super(void) { git_repository *repo = cl_git_sandbox_init("super"); diff --git a/tests/submodule/submodule_helpers.h b/tests/submodule/submodule_helpers.h index 42b14a7bc..d112b0c77 100644 --- a/tests/submodule/submodule_helpers.h +++ b/tests/submodule/submodule_helpers.h @@ -3,6 +3,7 @@ extern void rewrite_gitmodules(const char *workdir); /* these will automatically set a cleanup callback */ extern git_repository *setup_fixture_submodules(void); extern git_repository *setup_fixture_submod2(void); +extern git_repository *setup_fixture_submod3(void); extern git_repository *setup_fixture_submodule_simple(void); extern git_repository *setup_fixture_super(void); extern git_repository *setup_fixture_submodule_with_path(void); diff --git a/tests/threads/basic.c b/tests/threads/basic.c index 9c342bc42..a9310bbd4 100644 --- a/tests/threads/basic.c +++ b/tests/threads/basic.c @@ -48,3 +48,36 @@ void test_threads_basic__set_error(void) { run_in_parallel(1, 4, set_error, NULL, NULL); } + +#ifdef GIT_THREADS +static void *return_normally(void *param) +{ + return param; +} + +static void *exit_abruptly(void *param) +{ + git_thread_exit(param); + return NULL; +} +#endif + +void test_threads_basic__exit(void) +{ +#ifndef GIT_THREADS + clar__skip(); +#else + git_thread thread; + void *result; + + /* Ensure that the return value of the threadproc is returned. */ + cl_git_pass(git_thread_create(&thread, return_normally, (void *)424242)); + cl_git_pass(git_thread_join(&thread, &result)); + cl_assert_equal_sz(424242, (size_t)result); + + /* Ensure that the return value of `git_thread_exit` is returned. */ + cl_git_pass(git_thread_create(&thread, return_normally, (void *)232323)); + cl_git_pass(git_thread_join(&thread, &result)); + cl_assert_equal_sz(232323, (size_t)result); +#endif +} diff --git a/tests/threads/refdb.c b/tests/threads/refdb.c index f869bcb44..94c5f5057 100644 --- a/tests/threads/refdb.c +++ b/tests/threads/refdb.c @@ -5,6 +5,12 @@ static git_repository *g_repo; static int g_expected = 0; +#ifdef GIT_WIN32 +static bool concurrent_compress = false; +#else +static bool concurrent_compress = true; +#endif + void test_threads_refdb__initialize(void) { g_repo = NULL; @@ -18,14 +24,28 @@ void test_threads_refdb__cleanup(void) #define REPEAT 20 #define THREADS 20 +/* Number of references to create or delete in each thread */ +#define NREFS 10 + +struct th_data { + cl_git_thread_err error; + int id; + const char *path; +}; static void *iterate_refs(void *arg) { + struct th_data *data = (struct th_data *) arg; git_reference_iterator *i; git_reference *ref; - int count = 0; + int count = 0, error; + git_repository *repo; - cl_git_pass(git_reference_iterator_new(&i, g_repo)); + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); + do { + error = git_reference_iterator_new(&i, repo); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); for (count = 0; !git_reference_next(&ref, i); ++count) { cl_assert(ref != NULL); @@ -37,112 +57,92 @@ static void *iterate_refs(void *arg) git_reference_iterator_free(i); + git_repository_free(repo); giterr_clear(); return arg; } -void test_threads_refdb__iterator(void) -{ - int r, t; - git_thread th[THREADS]; - int id[THREADS]; - git_oid head; - git_reference *ref; - char name[128]; - git_refdb *refdb; - - g_repo = cl_git_sandbox_init("testrepo2"); - - cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); - - /* make a bunch of references */ - - for (r = 0; r < 200; ++r) { - p_snprintf(name, sizeof(name), "refs/heads/direct-%03d", r); - cl_git_pass(git_reference_create(&ref, g_repo, name, &head, 0, NULL)); - git_reference_free(ref); - } - - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); - git_refdb_free(refdb); - - g_expected = 206; - - for (r = 0; r < REPEAT; ++r) { - g_repo = cl_git_sandbox_reopen(); /* reopen to flush caches */ - - for (t = 0; t < THREADS; ++t) { - id[t] = t; -#ifdef GIT_THREADS - cl_git_pass(git_thread_create(&th[t], iterate_refs, &id[t])); -#else - th[t] = t; - iterate_refs(&id[t]); -#endif - } - -#ifdef GIT_THREADS - for (t = 0; t < THREADS; ++t) { - cl_git_pass(git_thread_join(&th[t], NULL)); - } -#endif - - memset(th, 0, sizeof(th)); - } -} - static void *create_refs(void *arg) { - int *id = arg, i; + int i, error; + struct th_data *data = (struct th_data *) arg; git_oid head; char name[128]; - git_reference *ref[10]; + git_reference *ref[NREFS]; + git_repository *repo; - cl_git_pass(git_reference_name_to_id(&head, g_repo, "HEAD")); + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); - for (i = 0; i < 10; ++i) { - p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", *id, i); - cl_git_pass(git_reference_create(&ref[i], g_repo, name, &head, 0, NULL)); + do { + error = git_reference_name_to_id(&head, repo, "HEAD"); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); - if (i == 5) { + for (i = 0; i < NREFS; ++i) { + p_snprintf(name, sizeof(name), "refs/heads/thread-%03d-%02d", data->id, i); + do { + error = git_reference_create(&ref[i], repo, name, &head, 0, NULL); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); + + if (concurrent_compress && i == NREFS/2) { git_refdb *refdb; - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); + cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); + do { + error = git_refdb_compress(refdb); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); git_refdb_free(refdb); } } - for (i = 0; i < 10; ++i) + for (i = 0; i < NREFS; ++i) git_reference_free(ref[i]); + git_repository_free(repo); + giterr_clear(); return arg; } static void *delete_refs(void *arg) { - int *id = arg, i; + int i, error; + struct th_data *data = (struct th_data *) arg; git_reference *ref; char name[128]; + git_repository *repo; - for (i = 0; i < 10; ++i) { + cl_git_thread_pass(data, git_repository_open(&repo, data->path)); + + for (i = 0; i < NREFS; ++i) { p_snprintf( - name, sizeof(name), "refs/heads/thread-%03d-%02d", (*id) & ~0x3, i); + name, sizeof(name), "refs/heads/thread-%03d-%02d", (data->id) & ~0x3, i); - if (!git_reference_lookup(&ref, g_repo, name)) { - cl_git_pass(git_reference_delete(ref)); + if (!git_reference_lookup(&ref, repo, name)) { + do { + error = git_reference_delete(ref); + } while (error == GIT_ELOCKED); + /* Sometimes we race with other deleter threads */ + if (error == GIT_ENOTFOUND) + error = 0; + + cl_git_thread_pass(data, error); git_reference_free(ref); } - if (i == 5) { + if (concurrent_compress && i == NREFS/2) { git_refdb *refdb; - cl_git_pass(git_repository_refdb(&refdb, g_repo)); - cl_git_pass(git_refdb_compress(refdb)); + cl_git_thread_pass(data, git_repository_refdb(&refdb, repo)); + do { + error = git_refdb_compress(refdb); + } while (error == GIT_ELOCKED); + cl_git_thread_pass(data, error); git_refdb_free(refdb); } } + git_repository_free(repo); giterr_clear(); return arg; } @@ -150,7 +150,7 @@ static void *delete_refs(void *arg) void test_threads_refdb__edit_while_iterate(void) { int r, t; - int id[THREADS]; + struct th_data th_data[THREADS]; git_oid head; git_reference *ref; char name[128]; @@ -189,33 +189,32 @@ void test_threads_refdb__edit_while_iterate(void) default: fn = iterate_refs; break; } - id[t] = t; + th_data[t].id = t; + th_data[t].path = git_repository_path(g_repo); - /* It appears with all reflog writing changes, etc., that this - * test has started to fail quite frequently, so let's disable it - * for now by just running on a single thread... - */ -/* #ifdef GIT_THREADS */ -/* cl_git_pass(git_thread_create(&th[t], fn, &id[t])); */ -/* #else */ - fn(&id[t]); -/* #endif */ +#ifdef GIT_THREADS + cl_git_pass(git_thread_create(&th[t], fn, &th_data[t])); +#else + fn(&th_data[t]); +#endif } #ifdef GIT_THREADS -/* for (t = 0; t < THREADS; ++t) { */ -/* cl_git_pass(git_thread_join(th[t], NULL)); */ -/* } */ + for (t = 0; t < THREADS; ++t) { + cl_git_pass(git_thread_join(&th[t], NULL)); + cl_git_thread_check(&th_data[t]); + } memset(th, 0, sizeof(th)); for (t = 0; t < THREADS; ++t) { - id[t] = t; - cl_git_pass(git_thread_create(&th[t], iterate_refs, &id[t])); + th_data[t].id = t; + cl_git_pass(git_thread_create(&th[t], iterate_refs, &th_data[t])); } for (t = 0; t < THREADS; ++t) { cl_git_pass(git_thread_join(&th[t], NULL)); + cl_git_thread_check(&th_data[t]); } #endif }