diff --git a/.travis.yml b/.travis.yml index bab02bb44ab..f93a9214ca0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,10 +46,11 @@ after_success: - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install valgrind; fi - if [ "$TRAVIS_OS_NAME" = "linux" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi -# Only watch the development branch +# Watch development and stable branches branches: only: - development + - /^maint\/.*$/ # Notify development list when needed notifications: diff --git a/src/checkout.c b/src/checkout.c index 20763fd35b0..1c5fdd34024 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1736,10 +1736,12 @@ static int checkout_write_merge( checkout_conflictdata *conflict) { git_buf our_label = GIT_BUF_INIT, their_label = GIT_BUF_INIT, - path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT; + path_suffixed = GIT_BUF_INIT, path_workdir = GIT_BUF_INIT, + in_data = GIT_BUF_INIT, out_data = GIT_BUF_INIT; git_merge_file_options opts = GIT_MERGE_FILE_OPTIONS_INIT; git_merge_file_result result = {0}; git_filebuf output = GIT_FILEBUF_INIT; + git_filter_list *fl = NULL; int error = 0; if (data->opts.checkout_strategy & GIT_CHECKOUT_CONFLICT_STYLE_DIFF3) @@ -1785,13 +1787,29 @@ static int checkout_write_merge( (error = checkout_safe_for_update_only(git_buf_cstr(&path_workdir), result.mode)) <= 0) goto done; + if (!data->opts.disable_filters) { + in_data.ptr = (char *)result.ptr; + in_data.size = result.len; + + if ((error = git_filter_list_load(&fl, data->repo, NULL, git_buf_cstr(&path_workdir), + GIT_FILTER_TO_WORKTREE, GIT_FILTER_OPT_DEFAULT)) < 0 || + (error = git_filter_list_apply_to_data(&out_data, fl, &in_data)) < 0) + goto done; + } else { + out_data.ptr = (char *)result.ptr; + out_data.size = result.len; + } + if ((error = git_futils_mkpath2file(path_workdir.ptr, 0755)) < 0 || - (error = git_filebuf_open(&output, path_workdir.ptr, GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || - (error = git_filebuf_write(&output, result.ptr, result.len)) < 0 || + (error = git_filebuf_open(&output, git_buf_cstr(&path_workdir), GIT_FILEBUF_DO_NOT_BUFFER, result.mode)) < 0 || + (error = git_filebuf_write(&output, out_data.ptr, out_data.size)) < 0 || (error = git_filebuf_commit(&output)) < 0) goto done; done: + git_filter_list_free(fl); + + git_buf_free(&out_data); git_buf_free(&our_label); git_buf_free(&their_label); diff --git a/src/config_file.c b/src/config_file.c index 8f55c42f348..1f73e7e11f8 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -767,12 +767,17 @@ static int config_readonly_open(git_config_backend *cfg, git_config_level_t leve { diskfile_readonly_backend *b = (diskfile_readonly_backend *) cfg; diskfile_backend *src = b->snapshot_from; + diskfile_header *src_header = &src->header; refcounted_strmap *src_map; + int error; + + if (!src_header->readonly && (error = config_refresh(&src_header->parent)) < 0) + return error; /* We're just copying data, don't care about the level */ GIT_UNUSED(level); - src_map = refcounted_strmap_take(&src->header); + src_map = refcounted_strmap_take(src_header); b->header.values = src_map; return 0; diff --git a/src/global.c b/src/global.c index f8b387676b4..5238af9d2b8 100644 --- a/src/global.c +++ b/src/global.c @@ -61,8 +61,12 @@ void openssl_locking_function(int mode, int n, const char *file, int line) git_mutex_unlock(&openssl_locks[n]); } } -#endif +static void shutdown_ssl(void) +{ + git__free(openssl_locks); +} +#endif static void init_ssl(void) { @@ -110,6 +114,8 @@ static void init_ssl(void) CRYPTO_set_locking_callback(openssl_locking_function); } + + git__on_shutdown(shutdown_ssl); # endif #endif } diff --git a/src/path.c b/src/path.c index 5beab97edaf..f7d05dc916b 100644 --- a/src/path.c +++ b/src/path.c @@ -494,23 +494,33 @@ bool git_path_is_empty_dir(const char *path) WIN32_FIND_DATAW findData; HANDLE hFind = FindFirstFileW(filter_w, &findData); + /* FindFirstFile will fail if there are no children to the given + * path, which can happen if the given path is a file (and obviously + * has no children) or if the given path is an empty mount point. + * (Most directories have at least directory entries '.' and '..', + * but ridiculously another volume mounted in another drive letter's + * path space do not, and thus have nothing to enumerate.) If + * FindFirstFile fails, check if this is a directory-like thing + * (a mount point). + */ + if (hFind == INVALID_HANDLE_VALUE) + return git_path_isdir(path); + /* If the find handle was created successfully, then it's a directory */ - if (hFind != INVALID_HANDLE_VALUE) { - empty = true; - - do { - /* Allow the enumeration to return . and .. and still be considered - * empty. In the special case of drive roots (i.e. C:\) where . and - * .. do not occur, we can still consider the path to be an empty - * directory if there's nothing there. */ - if (!git_path_is_dot_or_dotdotW(findData.cFileName)) { - empty = false; - break; - } - } while (FindNextFileW(hFind, &findData)); - - FindClose(hFind); - } + empty = true; + + do { + /* Allow the enumeration to return . and .. and still be considered + * empty. In the special case of drive roots (i.e. C:\) where . and + * .. do not occur, we can still consider the path to be an empty + * directory if there's nothing there. */ + if (!git_path_is_dot_or_dotdotW(findData.cFileName)) { + empty = false; + break; + } + } while (FindNextFileW(hFind, &findData)); + + FindClose(hFind); } return empty; diff --git a/src/repository.c b/src/repository.c index e8d50aed342..d52c0c60f31 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1551,8 +1551,10 @@ int git_repository_head_unborn(git_repository *repo) error = git_repository_head(&ref, repo); git_reference_free(ref); - if (error == GIT_EUNBORNBRANCH) + if (error == GIT_EUNBORNBRANCH) { + giterr_clear(); return 1; + } if (error < 0) return -1; diff --git a/src/tag.c b/src/tag.c index d7b531d3408..ca2ed531bbf 100644 --- a/src/tag.c +++ b/src/tag.c @@ -406,8 +406,9 @@ int git_tag_delete(git_repository *repo, const char *tag_name) if (error < 0) return error; - if ((error = git_reference_delete(tag_ref)) == 0) - git_reference_free(tag_ref); + error = git_reference_delete(tag_ref); + + git_reference_free(tag_ref); return error; } diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 34938431a98..c6169e816fe 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -110,6 +110,11 @@ GIT_INLINE(time_t) filetime_to_time_t(const FILETIME *ft) return (time_t)winTime; } +static bool path_is_volume(wchar_t *target, size_t target_len) +{ + return (target_len && wcsncmp(target, L"\\??\\Volume{", 11) == 0); +} + /* On success, returns the length, in characters, of the path stored in dest. * On failure, returns a negative value. */ static int readlink_w( @@ -156,7 +161,13 @@ static int readlink_w( goto on_error; } - if (target_len) { + if (path_is_volume(target, target_len)) { + /* This path is a reparse point that represents another volume mounted + * at this location, it is not a symbolic link our input was canonical. + */ + errno = EINVAL; + error = -1; + } else if (target_len) { /* The path may need to have a prefix removed. */ target_len = git_win32__canonicalize_path(target, target_len); diff --git a/tests/checkout/index.c b/tests/checkout/index.c index 7f641b32979..3c01e241135 100644 --- a/tests/checkout/index.c +++ b/tests/checkout/index.c @@ -618,3 +618,72 @@ void test_checkout_index__can_get_repo_from_index(void) git_index_free(index); } + +static void add_conflict(void) +{ + git_index *index; + git_index_entry entry; + + memset(&entry, 0, sizeof(git_index_entry)); + + cl_git_pass(git_repository_index(&index, g_repo)); + + entry.mode = 0100644; + entry.path = "conflicting.txt"; + + git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46"); + entry.flags = (1 << GIT_IDXENTRY_STAGESHIFT); + cl_git_pass(git_index_add(index, &entry)); + + git_oid_fromstr(&entry.id, "4e886e602529caa9ab11d71f86634bd1b6e0de10"); + entry.flags = (2 << GIT_IDXENTRY_STAGESHIFT); + cl_git_pass(git_index_add(index, &entry)); + + git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863"); + entry.flags = (3 << GIT_IDXENTRY_STAGESHIFT); + cl_git_pass(git_index_add(index, &entry)); + + cl_git_pass(git_index_write(index)); + git_index_free(index); +} + +void test_checkout_index__writes_conflict_file(void) +{ + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_buf conflicting_buf = GIT_BUF_INIT; + + add_conflict(); + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, + "<<<<<<< ours\n" + "this file is changed in master and branch\n" + "=======\n" + "this file is changed in branch and master\n" + ">>>>>>> theirs\n") == 0); + git_buf_free(&conflicting_buf); +} + +void test_checkout_index__conflicts_honor_coreautocrlf(void) +{ +#ifdef GIT_WIN32 + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + git_buf conflicting_buf = GIT_BUF_INIT; + + cl_git_pass(p_unlink("./testrepo/.gitattributes")); + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + add_conflict(); + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + cl_git_pass(git_futils_readbuffer(&conflicting_buf, "testrepo/conflicting.txt")); + cl_assert(strcmp(conflicting_buf.ptr, + "<<<<<<< ours\r\n" + "this file is changed in master and branch\r\n" + "=======\r\n" + "this file is changed in branch and master\r\n" + ">>>>>>> theirs\r\n") == 0); + git_buf_free(&conflicting_buf); +#endif +} diff --git a/tests/config/snapshot.c b/tests/config/snapshot.c index c9f15921a6e..3ea07c11841 100644 --- a/tests/config/snapshot.c +++ b/tests/config/snapshot.c @@ -3,7 +3,7 @@ void test_config_snapshot__create_snapshot(void) { int32_t tmp; - git_config *cfg, *snapshot; + git_config *cfg, *snapshot, *new_snapshot; const char *filename = "config-ext-change"; cl_git_mkfile(filename, "[old]\nvalue = 5\n"); @@ -23,7 +23,21 @@ void test_config_snapshot__create_snapshot(void) cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value")); cl_assert_equal_i(5, tmp); + + /* Change the value on the file itself (simulate external process) */ + cl_git_mkfile(filename, "[old]\nvalue = 999\n"); + + cl_git_pass(git_config_snapshot(&new_snapshot, cfg)); + + /* New snapshot should see new value */ + cl_git_pass(git_config_get_int32(&tmp, new_snapshot, "old.value")); + cl_assert_equal_i(999, tmp); + + /* Old snapshot should still have the old value */ + cl_git_pass(git_config_get_int32(&tmp, snapshot, "old.value")); + cl_assert_equal_i(5, tmp); + git_config_free(new_snapshot); git_config_free(snapshot); git_config_free(cfg); } diff --git a/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 new file mode 100644 index 00000000000..d10ca636b6b Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 differ diff --git a/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 b/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 new file mode 100644 index 00000000000..53168a038b7 Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 differ diff --git a/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 new file mode 100644 index 00000000000..0b3611ae4c9 Binary files /dev/null and b/tests/resources/testrepo/.gitted/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 differ