From bdec336301c348404b0590025025420febae5b98 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 3 Aug 2015 17:48:33 -0500 Subject: [PATCH 001/101] win32: ensure hidden files can be staged --- src/repository.c | 4 ++-- src/win32/w32_util.c | 34 +++++++++++++++++++++++++++++----- src/win32/w32_util.h | 16 +++++++++++++--- tests/index/addall.c | 35 +++++++++++++++++++++++++++++++++++ tests/index/bypath.c | 26 ++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 10 deletions(-) diff --git a/src/repository.c b/src/repository.c index 08f4baa20c4..0f37cfbfe18 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1279,7 +1279,7 @@ static int repo_write_template( #ifdef GIT_WIN32 if (!error && hidden) { - if (git_win32__sethidden(path.ptr) < 0) + if (git_win32__set_hidden(path.ptr, true) < 0) error = -1; } #else @@ -1373,7 +1373,7 @@ static int repo_init_structure( /* Hide the ".git" directory */ #ifdef GIT_WIN32 if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) { - if (git_win32__sethidden(repo_dir) < 0) { + if (git_win32__set_hidden(repo_dir, true) < 0) { giterr_set(GITERR_OS, "Failed to mark Git repository folder as hidden"); return -1; diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c index 2e52525d5b9..60311bb50d5 100644 --- a/src/win32/w32_util.c +++ b/src/win32/w32_util.c @@ -48,10 +48,10 @@ bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src) * @param path The path which should receive the +H bit. * @return 0 on success; -1 on failure */ -int git_win32__sethidden(const char *path) +int git_win32__set_hidden(const char *path, bool hidden) { git_win32_path buf; - DWORD attrs; + DWORD attrs, newattrs; if (git_win32_path_from_utf8(buf, path) < 0) return -1; @@ -62,11 +62,35 @@ int git_win32__sethidden(const char *path) if (attrs == INVALID_FILE_ATTRIBUTES) return -1; - /* If the item isn't already +H, add the bit */ - if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 && - !SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN)) + if (hidden) + newattrs = attrs | FILE_ATTRIBUTE_HIDDEN; + else + newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN; + + if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) { + giterr_set(GITERR_OS, "Failed to %s hidden bit for '%s'", + hidden ? "set" : "unset", path); + return -1; + } + + return 0; +} + +int git_win32__hidden(bool *out, const char *path) +{ + git_win32_path buf; + DWORD attrs; + + if (git_win32_path_from_utf8(buf, path) < 0) + return -1; + + attrs = GetFileAttributesW(buf); + + /* Ensure the path exists */ + if (attrs == INVALID_FILE_ATTRIBUTES) return -1; + *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; return 0; } diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 377d651a8d4..8db3afbec49 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -40,12 +40,22 @@ GIT_INLINE(bool) git_win32__isalpha(wchar_t c) bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src); /** - * Ensures the given path (file or folder) has the +H (hidden) attribute set. + * Ensures the given path (file or folder) has the +H (hidden) attribute set + * or unset. * - * @param path The path which should receive the +H bit. + * @param path The path that should receive the +H bit. + * @param hidden true to set +H, false to unset it * @return 0 on success; -1 on failure */ -int git_win32__sethidden(const char *path); +extern int git_win32__set_hidden(const char *path, bool hidden); + +/** + * Determines if the given file or folder has the hidden attribute set. + * @param hidden pointer to store hidden value + * @param path The path that should be queried for hiddenness. + * @return 0 on success or an error code. + */ +extern int git_win32__hidden(bool *hidden, const char *path); /** * Removes any trailing backslashes from a path, except in the case of a drive diff --git a/tests/index/addall.c b/tests/index/addall.c index 9ddb27f95f3..7b7a178d1b6 100644 --- a/tests/index/addall.c +++ b/tests/index/addall.c @@ -307,6 +307,41 @@ void test_index_addall__files_in_folders(void) git_index_free(index); } +void test_index_addall__hidden_files(void) +{ + git_index *index; + + GIT_UNUSED(index); + +#ifdef GIT_WIN32 + addall_create_test_repo(true); + + cl_git_pass(git_repository_index(&index, g_repo)); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0); + + cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one"); + cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one"); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true)); + cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true)); + + check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0); + + cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL)); + check_stat_data(index, TEST_DIR "/file.bar", true); + check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0); + + git_index_free(index); +#endif +} + static int addall_match_prefix( const char *path, const char *matched_pathspec, void *payload) { diff --git a/tests/index/bypath.c b/tests/index/bypath.c index 9706a8833e2..b607e173282 100644 --- a/tests/index/bypath.c +++ b/tests/index/bypath.c @@ -46,3 +46,29 @@ void test_index_bypath__add_submodule_unregistered(void) cl_assert_equal_s(sm_head, git_oid_tostr_s(&entry->id)); cl_assert_equal_s(sm_name, entry->path); } + +void test_index_bypath__add_hidden(void) +{ + const git_index_entry *entry; + bool hidden; + + GIT_UNUSED(entry); + GIT_UNUSED(hidden); + +#ifdef GIT_WIN32 + cl_git_mkfile("submod2/hidden_file", "you can't see me"); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(!hidden); + + cl_git_pass(git_win32__set_hidden("submod2/hidden_file", true)); + + cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file")); + cl_assert(hidden); + + cl_git_pass(git_index_add_bypath(g_idx, "hidden_file")); + + cl_assert(entry = git_index_get_bypath(g_idx, "hidden_file", 0)); + cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode); +#endif +} From 241414ee33c627092a483bc56d46f5a0d9c16ca9 Mon Sep 17 00:00:00 2001 From: Max Leske Date: Fri, 14 Aug 2015 15:42:59 +0200 Subject: [PATCH 002/101] added a single line of additional error reporting from libssh2 when failing to retrieve the list of authentication methods --- src/transports/ssh.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 8f5a7164b81..ffa4a24a7e9 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -756,8 +756,10 @@ static int list_auth_methods(int *out, LIBSSH2_SESSION *session, const char *use list = libssh2_userauth_list(session, username, strlen(username)); /* either error, or the remote accepts NONE auth, which is bizarre, let's punt */ - if (list == NULL && !libssh2_userauth_authenticated(session)) + if (list == NULL && !libssh2_userauth_authenticated(session)) { + ssh_error(session, "Failed to retrieve list of SSH authentication methods"); return -1; + } ptr = list; while (ptr) { From ac02a69470f149be5c556eb607d8cb3dcc21771f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 14 Aug 2015 21:06:09 +0200 Subject: [PATCH 003/101] Add a hashmap for index entries They are hashed case-insensitively and take the stage into account. --- src/idxmap.h | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/idxmap.h diff --git a/src/idxmap.h b/src/idxmap.h new file mode 100644 index 00000000000..74304bb97a3 --- /dev/null +++ b/src/idxmap.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_idxmap_h__ +#define INCLUDE_idxmap_h__ + +#include +#include "common.h" +#include "git2/index.h" + +#define kmalloc git__malloc +#define kcalloc git__calloc +#define krealloc git__realloc +#define kreallocarray git__reallocarray +#define kfree git__free +#include "khash.h" + +__KHASH_TYPE(idx, const git_index_entry *, git_index_entry *) +__KHASH_TYPE(idxicase, const git_index_entry *, git_index_entry *) + +typedef khash_t(idx) git_idxmap; +typedef khash_t(idxicase) git_idxmap_icase; + +typedef khiter_t git_idxmap_iter; + +/* This is __ac_X31_hash_string but with tolower and it takes the entry's stage into account */ +static kh_inline khint_t idxentry_hash(const git_index_entry *e) +{ + const char *s = e->path; + khint_t h = (khint_t)git__tolower(*s); + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)git__tolower(*s); + return h + GIT_IDXENTRY_STAGE(e); +} + +#define idxentry_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcmp(a->path, b->path) == 0) +#define idxentry_icase_equal(a, b) (GIT_IDXENTRY_STAGE(a) == GIT_IDXENTRY_STAGE(b) && strcasecmp(a->path, b->path) == 0) + +#define GIT__USE_IDXMAP \ + __KHASH_IMPL(idx, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_equal) + +#define GIT__USE_IDXMAP_ICASE \ + __KHASH_IMPL(idxicase, static kh_inline, const git_index_entry *, git_index_entry *, 1, idxentry_hash, idxentry_icase_equal) + +#define git_idxmap_alloc(hp) \ + ((*(hp) = kh_init(idx)) == NULL) ? giterr_set_oom(), -1 : 0 + +#define git_idxmap_icase_alloc(hp) \ + ((*(hp) = kh_init(idxicase)) == NULL) ? giterr_set_oom(), -1 : 0 + +#define git_idxmap_insert(h, key, val, rval) do { \ + khiter_t __pos = kh_put(idx, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_idxmap_icase_insert(h, key, val, rval) do { \ + khiter_t __pos = kh_put(idxicase, h, key, &rval); \ + if (rval >= 0) { \ + if (rval == 0) kh_key(h, __pos) = key; \ + kh_val(h, __pos) = val; \ + } } while (0) + +#define git_idxmap_lookup_index(h, k) kh_get(idx, h, k) +#define git_idxmap_icase_lookup_index(h, k) kh_get(idxicase, h, k) +#define git_idxmap_value_at(h, idx) kh_val(h, idx) +#define git_idxmap_valid_index(h, idx) (idx != kh_end(h)) +#define git_idxmap_has_data(h, idx) kh_exist(h, idx) + +#define git_idxmap_free(h) kh_destroy(idx, h), h = NULL +#define git_idxmap_clear(h) kh_clear(idx, h) + +#define git_idxmap_delete_at(h, id) kh_del(idx, h, id) +#define git_idxmap_icase_delete_at(h, id) kh_del(idxicase, h, id) + +#define git_idxmap_delete(h, key) do { \ + khiter_t __pos = git_idxmap_lookup_index(h, key); \ + if (git_idxmap_valid_index(h, __pos)) \ + git_idxmap_delete_at(h, __pos); } while (0) + +#define git_idxmap_icase_delete(h, key) do { \ + khiter_t __pos = git_idxmap_icase_lookup_index(h, key); \ + if (git_idxmap_valid_index(h, __pos)) \ + git_idxmap_icase_delete_at(h, __pos); } while (0) + +#define git_idxmap_begin kh_begin +#define git_idxmap_end kh_end + +#endif From c232d6c32df90e009dcb8d79309d2c9b9f51b77a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 14 Aug 2015 21:06:51 +0200 Subject: [PATCH 004/101] index: add tests around case switching We were missing tests for switching the case-sensitivity of an index in-memory and then looking up entries in it. --- tests/index/filemodes.c | 1 + tests/index/tests.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index b3907996b48..6442d7755f4 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -239,6 +239,7 @@ void test_index_filemodes__invalid(void) cl_git_pass(git_repository_index(&index, g_repo)); + GIT_IDXENTRY_STAGE_SET(&entry, 0); entry.path = "foo"; entry.mode = GIT_OBJ_BLOB; cl_git_fail(git_index_add(index, &entry)); diff --git a/tests/index/tests.c b/tests/index/tests.c index e1ff12ad0ff..f1a0578533c 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -792,10 +792,43 @@ void test_index_tests__reload_while_ignoring_case(void) cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEXCAP_IGNORE_CASE)); cl_git_pass(git_index_read(index, true)); cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert_equal_p(NULL, git_index_get_bypath(index, ".header", 0)); cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); cl_git_pass(git_index_read(index, true)); cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(git_index_get_bypath(index, ".HEADER", 0)); + cl_assert(git_index_get_bypath(index, ".header", 0)); + + git_index_free(index); +} + +void test_index_tests__change_icase_on_instance(void) +{ + git_index *index; + unsigned int caps; + const git_index_entry *e; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + + caps = git_index_caps(index); + cl_git_pass(git_index_set_caps(index, caps &= ~GIT_INDEXCAP_IGNORE_CASE)); + cl_assert_equal_i(false, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "src/common.h", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "SRC/Common.h", 0)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_p(NULL, e = git_index_get_bypath(index, "copying", 0)); + + cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); + cl_assert_equal_i(true, index->ignore_case); + cl_git_pass(git_vector_verify_sorted(&index->entries)); + cl_assert(e = git_index_get_bypath(index, "COPYING", 0)); + cl_assert_equal_s("COPYING", e->path); + cl_assert(e = git_index_get_bypath(index, "copying", 0)); + cl_assert_equal_s("COPYING", e->path); git_index_free(index); } From af1d5239a16976bd1b8d0a9358497f043bdfed14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 14 Aug 2015 21:10:12 +0200 Subject: [PATCH 005/101] index: keep a hash table as well as a vector of entries The hash table allows quick lookup of specific paths, while we use the vector for enumeration. --- src/index.c | 111 ++++++++++++++++++++++++++++++++++++++++++++-------- src/index.h | 2 + 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/index.c b/src/index.c index e424698bb96..e904ffcfebb 100644 --- a/src/index.c +++ b/src/index.c @@ -17,6 +17,7 @@ #include "pathspec.h" #include "ignore.h" #include "blob.h" +#include "idxmap.h" #include "git2/odb.h" #include "git2/oid.h" @@ -24,6 +25,9 @@ #include "git2/config.h" #include "git2/sys/index.h" +GIT__USE_IDXMAP +GIT__USE_IDXMAP_ICASE + static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, unsigned int flags, git_index_matched_path_cb cb, void *payload); @@ -425,6 +429,7 @@ int git_index_open(git_index **index_out, const char *index_path) } if (git_vector_init(&index->entries, 32, git_index_entry_cmp) < 0 || + git_idxmap_alloc(&index->entries_map) < 0 || git_vector_init(&index->names, 8, conflict_name_cmp) < 0 || git_vector_init(&index->reuc, 8, reuc_cmp) < 0 || git_vector_init(&index->deleted, 8, git_index_entry_cmp) < 0) @@ -462,6 +467,7 @@ static void index_free(git_index *index) assert(!git_atomic_get(&index->readers)); git_index_clear(index); + git_idxmap_free(index->entries_map); git_vector_free(&index->entries); git_vector_free(&index->names); git_vector_free(&index->reuc); @@ -508,6 +514,11 @@ static int index_remove_entry(git_index *index, size_t pos) if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); + if (index->ignore_case) + git_idxmap_icase_delete((khash_t(idxicase) *) index->entries_map, entry); + else + git_idxmap_delete(index->entries_map, entry); + error = git_vector_remove(&index->entries, pos); if (!error) { @@ -535,6 +546,7 @@ int git_index_clear(git_index *index) return -1; } + git_idxmap_clear(index->entries_map); while (!error && index->entries.length > 0) error = index_remove_entry(index, index->entries.length - 1); index_free_deleted(index); @@ -804,16 +816,24 @@ const git_index_entry *git_index_get_byindex( const git_index_entry *git_index_get_bypath( git_index *index, const char *path, int stage) { - size_t pos; + khiter_t pos; + git_index_entry key = {{ 0 }}; assert(index); - if (index_find(&pos, index, path, 0, stage, true) < 0) { - giterr_set(GITERR_INDEX, "Index does not contain %s", path); - return NULL; - } + key.path = path; + GIT_IDXENTRY_STAGE_SET(&key, stage); + + if (index->ignore_case) + pos = git_idxmap_icase_lookup_index((khash_t(idxicase) *) index->entries_map, &key); + else + pos = git_idxmap_lookup_index(index->entries_map, &key); + + if (git_idxmap_valid_index(index->entries_map, pos)) + return git_idxmap_value_at(index->entries_map, pos); - return git_index_get_byindex(index, pos); + giterr_set(GITERR_INDEX, "Index does not contain %s", path); + return NULL; } void git_index_entry__init_from_stat( @@ -1139,6 +1159,13 @@ static int index_insert( * check for dups, this is actually cheaper in the long run.) */ error = git_vector_insert_sorted(&index->entries, entry, index_no_dups); + + if (error == 0) { + if (index->ignore_case) + git_idxmap_icase_insert((khash_t(idxicase) *) index->entries_map, entry, entry, error); else + git_idxmap_insert(index->entries_map, entry, entry, error); + + } } if (error < 0) { @@ -1364,12 +1391,20 @@ int git_index_remove(git_index *index, const char *path, int stage) { int error; size_t position; + git_index_entry remove_key = {{ 0 }}; if (git_mutex_lock(&index->lock) < 0) { giterr_set(GITERR_OS, "Failed to lock index"); return -1; } + remove_key.path = path; + GIT_IDXENTRY_STAGE_SET(&remove_key, stage); + if (index->ignore_case) + git_idxmap_icase_delete((khash_t(idxicase) *) index->entries_map, &remove_key); + else + git_idxmap_delete(index->entries_map, &remove_key); + if (index_find(&position, index, path, 0, stage, false) < 0) { giterr_set( GITERR_INDEX, "Index does not contain %s at stage %d", path, stage); @@ -2180,6 +2215,11 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) assert(!index->entries.length); + if (index->ignore_case) + kh_resize(idxicase, (khash_t(idxicase) *) index->entries_map, header.entry_count); + else + kh_resize(idx, index->entries_map, header.entry_count); + /* Parse all the entries */ for (i = 0; i < header.entry_count && buffer_size > INDEX_FOOTER_SIZE; ++i) { git_index_entry *entry; @@ -2196,6 +2236,16 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) goto done; } + if (index->ignore_case) + git_idxmap_icase_insert((khash_t(idxicase) *) index->entries_map, entry, entry, error); + else + git_idxmap_insert(index->entries_map, entry, entry, error); + + if (error < 0) { + index_entry_free(entry); + goto done; + } + seek_forward(entry_size); } @@ -2610,7 +2660,13 @@ int git_index_read_tree(git_index *index, const git_tree *tree) { int error = 0; git_vector entries = GIT_VECTOR_INIT; + git_idxmap *entries_map; read_tree_data data; + size_t i; + git_index_entry *e; + + if (git_idxmap_alloc(&entries_map) < 0) + return -1; git_vector_set_cmp(&entries, index->entries._cmp); /* match sort */ @@ -2625,23 +2681,44 @@ int git_index_read_tree(git_index *index, const git_tree *tree) if (index_sort_if_needed(index, true) < 0) return -1; - error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data); + if ((error = git_tree_walk(tree, GIT_TREEWALK_POST, read_tree_cb, &data)) < 0) + goto cleanup; - if (!error) { - git_vector_sort(&entries); + if (index->ignore_case) + kh_resize(idxicase, (khash_t(idxicase) *) entries_map, entries.length); + else + kh_resize(idx, entries_map, entries.length); - if ((error = git_index_clear(index)) < 0) - /* well, this isn't good */; - else if (git_mutex_lock(&index->lock) < 0) { - giterr_set(GITERR_OS, "Unable to acquire index lock"); - error = -1; - } else { - git_vector_swap(&entries, &index->entries); - git_mutex_unlock(&index->lock); + git_vector_foreach(&entries, i, e) { + if (index->ignore_case) + git_idxmap_icase_insert((git_idxmap_icase *) entries_map, e, e, error); + else + git_idxmap_insert(entries_map, e, e, error); + + if (error < 0) { + giterr_set(GITERR_INDEX, "failed to insert entry into map"); + return error; } } + error = 0; + + git_vector_sort(&entries); + + if ((error = git_index_clear(index)) < 0) + /* well, this isn't good */; + else if (git_mutex_lock(&index->lock) < 0) { + giterr_set(GITERR_OS, "Unable to acquire index lock"); + error = -1; + } else { + git_vector_swap(&entries, &index->entries); + entries_map = git__swap(index->entries_map, entries_map); + git_mutex_unlock(&index->lock); + } + +cleanup: git_vector_free(&entries); + git_idxmap_free(entries_map); if (error < 0) return error; diff --git a/src/index.h b/src/index.h index 9c60b015c53..546e677befe 100644 --- a/src/index.h +++ b/src/index.h @@ -10,6 +10,7 @@ #include "fileops.h" #include "filebuf.h" #include "vector.h" +#include "idxmap.h" #include "tree-cache.h" #include "git2/odb.h" #include "git2/index.h" @@ -25,6 +26,7 @@ struct git_index { git_oid checksum; /* checksum at the end of the file */ git_vector entries; + git_idxmap *entries_map; git_mutex lock; /* lock held while entries is being changed */ git_vector deleted; /* deleted entries if readers > 0 */ From 2d1d2bb59f88956b894a6cdb002bc52a2528e4a8 Mon Sep 17 00:00:00 2001 From: Anders Borum Date: Wed, 5 Aug 2015 18:50:25 +0200 Subject: [PATCH 006/101] Include the 4 characters not recognised as hex-number when setting error in parse_len --- src/transports/smart_pkt.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index 9ccbd80852d..a6ae55d4848 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -351,7 +351,7 @@ static int unpack_pkt(git_pkt **out, const char *line, size_t len) static int32_t parse_len(const char *line) { char num[PKT_LEN_SIZE + 1]; - int i, error; + int i, k, error; int32_t len; const char *num_end; @@ -360,7 +360,14 @@ static int32_t parse_len(const char *line) for (i = 0; i < PKT_LEN_SIZE; ++i) { if (!isxdigit(num[i])) { - giterr_set(GITERR_NET, "Found invalid hex digit in length"); + /* Make sure there are no special characters before passing to error message */ + for (k = 0; k < PKT_LEN_SIZE; ++k) { + if(!isprint(num[k])) { + num[k] = '.'; + } + } + + giterr_set(GITERR_NET, "invalid hex digit in length: '%s'", num); return -1; } } From 6d0defe31c67a61f2f38894a2e4db7818a952d60 Mon Sep 17 00:00:00 2001 From: Vsevolod Parfenov Date: Mon, 24 Aug 2015 18:47:48 +0300 Subject: [PATCH 007/101] Fix 'If we're dealing with a directory' check --- src/ignore.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ignore.c b/src/ignore.c index 0031e4696ce..1f33687bcbb 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -97,7 +97,7 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match if (rule->containing_dir) { git_buf_puts(&buf, rule->containing_dir); } - if (!strchr(rule->pattern, '*')) + if (rule->flags & GIT_ATTR_FNMATCH_LEADINGDIR && !(rule->flags & GIT_ATTR_FNMATCH_NEGATIVE)) error = git_buf_printf(&buf, "%s/*", rule->pattern); else error = git_buf_puts(&buf, rule->pattern); From afe0ff1a5df1a4a8e71751aa9d33fbdfa60fed9c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 25 Aug 2015 11:20:37 -0400 Subject: [PATCH 008/101] COPYING: include winhttp definition copyright Include the copyright notice from the deps/winhttp/ sources. Move the LGPL to the bottom of the file (since multiple dependencies are LGPL licensed) and include the actual copyright notices from the regex sources. --- COPYING | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/COPYING b/COPYING index 6f6115e3bfc..1b88b9b8e36 100644 --- a/COPYING +++ b/COPYING @@ -407,6 +407,52 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ---------------------------------------------------------------------- The regex library (deps/regex/) is licensed under the GNU LGPL +(available at the end of this file). + +Definitions for data structures and routines for the regular +expression library. + +Copyright (C) 1985,1989-93,1995-98,2000,2001,2002,2003,2005,2006,2008 +Free Software Foundation, Inc. +This file is part of the GNU C Library. + +The GNU C Library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +The GNU C Library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with the GNU C Library; if not, write to the Free +Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +---------------------------------------------------------------------- + +The bundled winhttp definition files (deps/winhttp/) are licensed under +the GNU LGPL (available at the end of this file). + +Copyright (C) 2007 Francois Gouget + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + +---------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 From fec4a68cf9de222594583cf002fd1aea138ad50c Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 26 Aug 2015 23:08:03 +0200 Subject: [PATCH 009/101] Fix a typo [ci skip] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dd4b34474a..8f28cbf3b4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ v0.23 + 1 * `git_cert` descendent types now have a proper `parent` member -* It is the responsibility fo the refdb backend to decide what to do +* It is the responsibility of the refdb backend to decide what to do with the reflog on ref deletion. The file-based backend must delete it, a database-backed one may wish to archive it. From ed1c64464a4e3126eef5d74d2c14c19133fa9cd8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 28 Jul 2015 11:41:27 -0500 Subject: [PATCH 010/101] iterator: use an options struct instead of args --- src/checkout.c | 26 ++- src/diff.c | 44 +++-- src/index.c | 9 +- src/iterator.c | 45 ++--- src/iterator.h | 37 ++-- src/merge.c | 35 ++-- src/notes.c | 2 +- src/pathspec.c | 24 +-- src/refdb_fs.c | 6 +- src/stash.c | 29 +-- src/submodule.c | 4 +- tests/diff/iterator.c | 60 ++++-- tests/merge/trees/treediff.c | 12 +- tests/repo/iterator.c | 354 ++++++++++++++++++++++------------- tests/submodule/status.c | 6 +- tests/threads/iterator.c | 5 +- 16 files changed, 423 insertions(+), 275 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 4b3acbcce59..311040d598e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2471,11 +2471,12 @@ int git_checkout_iterator( { int error = 0; git_iterator *baseline = NULL, *workdir = NULL; + git_iterator_options baseline_opts = GIT_ITERATOR_OPTIONS_INIT, + workdir_opts = GIT_ITERATOR_OPTIONS_INIT; checkout_data data = {0}; git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT; uint32_t *actions = NULL; size_t *counts = NULL; - git_iterator_flag_t iterflags = 0; /* initialize structures and options */ error = checkout_data_init(&data, target, opts); @@ -2499,25 +2500,30 @@ int git_checkout_iterator( /* set up iterators */ - iterflags = git_iterator_ignore_case(target) ? + workdir_opts.flags = git_iterator_ignore_case(target) ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + workdir_opts.flags |= GIT_ITERATOR_DONT_AUTOEXPAND; + workdir_opts.start = data.pfx; + workdir_opts.end = data.pfx; if ((error = git_iterator_reset(target, data.pfx, data.pfx)) < 0 || (error = git_iterator_for_workdir_ext( &workdir, data.repo, data.opts.target_directory, index, NULL, - iterflags | GIT_ITERATOR_DONT_AUTOEXPAND, - data.pfx, data.pfx)) < 0) + &workdir_opts)) < 0) goto cleanup; + baseline_opts.flags = git_iterator_ignore_case(target) ? + GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE; + baseline_opts.start = data.pfx; + baseline_opts.end = data.pfx; + if (data.opts.baseline_index) { if ((error = git_iterator_for_index( - &baseline, data.opts.baseline_index, - iterflags, data.pfx, data.pfx)) < 0) + &baseline, data.opts.baseline_index, &baseline_opts)) < 0) goto cleanup; } else { if ((error = git_iterator_for_tree( - &baseline, data.opts.baseline, - iterflags, data.pfx, data.pfx)) < 0) + &baseline, data.opts.baseline, &baseline_opts)) < 0) goto cleanup; } @@ -2625,7 +2631,7 @@ int git_checkout_index( return error; GIT_REFCOUNT_INC(index); - if (!(error = git_iterator_for_index(&index_i, index, 0, NULL, NULL))) + if (!(error = git_iterator_for_index(&index_i, index, NULL))) error = git_checkout_iterator(index_i, index, opts); if (owned) @@ -2681,7 +2687,7 @@ int git_checkout_tree( if ((error = git_repository_index(&index, repo)) < 0) return error; - if (!(error = git_iterator_for_tree(&tree_i, tree, 0, NULL, NULL))) + if (!(error = git_iterator_for_tree(&tree_i, tree, NULL))) error = git_checkout_iterator(tree_i, index, opts); git_iterator_free(tree_i); diff --git a/src/diff.c b/src/diff.c index 44f62788086..58004db2105 100644 --- a/src/diff.c +++ b/src/diff.c @@ -1264,9 +1264,17 @@ int git_diff__from_iterators( return error; } -#define DIFF_FROM_ITERATORS(MAKE_FIRST, MAKE_SECOND) do { \ +#define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ git_iterator *a = NULL, *b = NULL; \ char *pfx = opts ? 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 (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ error = git_diff__from_iterators(diff, repo, a, b, opts); \ @@ -1280,8 +1288,8 @@ int git_diff_tree_to_tree( git_tree *new_tree, const git_diff_options *opts) { - int error = 0; git_iterator_flag_t iflag = GIT_ITERATOR_DONT_IGNORE_CASE; + int error = 0; assert(diff && repo); @@ -1293,8 +1301,8 @@ int git_diff_tree_to_tree( iflag = GIT_ITERATOR_IGNORE_CASE; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx), - git_iterator_for_tree(&b, new_tree, iflag, pfx, pfx) + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_tree(&b, new_tree, &b_opts), iflag ); return error; @@ -1318,10 +1326,10 @@ int git_diff_tree_to_index( git_index *index, const git_diff_options *opts) { - int error = 0; - bool index_ignore_case = false; 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); @@ -1331,8 +1339,8 @@ int git_diff_tree_to_index( index_ignore_case = index->ignore_case; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, iflag, pfx, pfx), - git_iterator_for_index(&b, index, iflag, pfx, pfx) + git_iterator_for_tree(&a, old_tree, &a_opts), iflag, + git_iterator_for_index(&b, index, &b_opts), iflag ); /* if index is in case-insensitive order, re-sort deltas to match */ @@ -1356,10 +1364,11 @@ int git_diff_index_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_index( - &a, index, GIT_ITERATOR_INCLUDE_CONFLICTS, pfx, pfx), - git_iterator_for_workdir( - &b, repo, index, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) + git_iterator_for_index(&a, 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) @@ -1383,9 +1392,8 @@ int git_diff_tree_to_workdir( return error; DIFF_FROM_ITERATORS( - git_iterator_for_tree(&a, old_tree, 0, pfx, pfx), - git_iterator_for_workdir( - &b, repo, index, old_tree, GIT_ITERATOR_DONT_AUTOEXPAND, pfx, pfx) + 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; @@ -1433,10 +1441,8 @@ int git_diff_index_to_index( assert(diff && old_index && new_index); DIFF_FROM_ITERATORS( - git_iterator_for_index( - &a, old_index, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx), - git_iterator_for_index( - &b, new_index, GIT_ITERATOR_DONT_IGNORE_CASE, pfx, pfx) + git_iterator_for_index(&a, old_index, &a_opts), GIT_ITERATOR_DONT_IGNORE_CASE, + git_iterator_for_index(&b, new_index, &b_opts), GIT_ITERATOR_DONT_IGNORE_CASE ); /* if index is in case-insensitive order, re-sort deltas to match */ diff --git a/src/index.c b/src/index.c index e424698bb96..a6a62e3270a 100644 --- a/src/index.c +++ b/src/index.c @@ -2658,6 +2658,7 @@ int git_index_read_index( remove_entries = GIT_VECTOR_INIT; 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; @@ -2667,10 +2668,10 @@ int git_index_read_index( (error = git_vector_init(&remove_entries, index->entries.length, NULL)) < 0) goto done; - if ((error = git_iterator_for_index(&index_iterator, - index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&new_iterator, - (git_index *)new_index, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_index(&index_iterator, index, &opts)) < 0 || + (error = git_iterator_for_index(&new_iterator, (git_index *)new_index, &opts)) < 0) goto done; if (((error = git_iterator_current(&old_entry, index_iterator)) < 0 && diff --git a/src/iterator.c b/src/iterator.c index 900ffdcaa8b..374caf96d1d 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -31,12 +31,15 @@ (P)->base.cb = &(P)->cb; \ ITERATOR_SET_CB(P,NAME_LC); \ (P)->base.repo = (REPO); \ - (P)->base.start = start ? git__strdup(start) : NULL; \ - (P)->base.end = end ? git__strdup(end) : NULL; \ - if ((start && !(P)->base.start) || (end && !(P)->base.end)) { \ + (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.prefixcomp = git__prefixcmp; \ - (P)->base.flags = flags & ~ITERATOR_CASE_FLAGS; \ + (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; \ } while (0) @@ -149,9 +152,7 @@ typedef struct { int git_iterator_for_nothing( git_iterator **iter, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { empty_iterator *i = git__calloc(1, sizeof(empty_iterator)); GITERR_CHECK_ALLOC(i); @@ -162,7 +163,7 @@ int git_iterator_for_nothing( ITERATOR_BASE_INIT(i, empty, EMPTY, NULL); - if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) i->base.flags |= GIT_ITERATOR_IGNORE_CASE; *iter = (git_iterator *)i; @@ -607,15 +608,13 @@ static int tree_iterator__create_root_frame(tree_iterator *ti, git_tree *tree) int git_iterator_for_tree( git_iterator **iter, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error; tree_iterator *ti; if (tree == NULL) - return git_iterator_for_nothing(iter, flags, start, end); + return git_iterator_for_nothing(iter, options); if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0) return error; @@ -625,7 +624,7 @@ int git_iterator_for_tree( ITERATOR_BASE_INIT(ti, tree, TREE, git_tree_owner(tree)); - if ((error = iterator__update_ignore_case((git_iterator *)ti, flags)) < 0) + if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0) goto fail; ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp; @@ -860,9 +859,7 @@ static void index_iterator__free(git_iterator *self) int git_iterator_for_index( git_iterator **iter, git_index *index, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error = 0; index_iterator *ii = git__calloc(1, sizeof(index_iterator)); @@ -876,7 +873,7 @@ int git_iterator_for_index( ITERATOR_BASE_INIT(ii, index, INDEX, git_index_owner(index)); - if ((error = iterator__update_ignore_case((git_iterator *)ii, flags)) < 0) { + if ((error = iterator__update_ignore_case((git_iterator *)ii, options ? options->flags : 0)) < 0) { git_iterator_free((git_iterator *)ii); return error; } @@ -1062,6 +1059,8 @@ static int dirload_with_stat( 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 */ @@ -1366,16 +1365,14 @@ static int fs_iterator__initialize( int git_iterator_for_filesystem( git_iterator **out, const char *root, - git_iterator_flag_t flags, - const char *start, - const char *end) + 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 ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) + if (options && (options->flags & GIT_ITERATOR_IGNORE_CASE) != 0) fi->base.flags |= GIT_ITERATOR_IGNORE_CASE; return fs_iterator__initialize(out, fi, root); @@ -1559,9 +1556,7 @@ int git_iterator_for_workdir_ext( const char *repo_workdir, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { int error, precompose = 0; workdir_iterator *wi; @@ -1583,7 +1578,7 @@ int git_iterator_for_workdir_ext( wi->fi.leave_dir_cb = workdir_iterator__leave_dir; wi->fi.update_entry_cb = workdir_iterator__update_entry; - if ((error = iterator__update_ignore_case((git_iterator *)wi, flags)) < 0 || + 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); diff --git a/src/iterator.h b/src/iterator.h index 893e5db5045..46e96f0448a 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -38,6 +38,17 @@ typedef enum { GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5), } git_iterator_flag_t; + +typedef struct { + const char *start; + const char *end; + + /* flags, from above */ + unsigned int flags; +} git_iterator_options; + +#define GIT_ITERATOR_OPTIONS_INIT {0} + typedef struct { int (*current)(const git_index_entry **, git_iterator *); int (*advance)(const git_index_entry **, git_iterator *); @@ -61,9 +72,7 @@ struct git_iterator { extern int git_iterator_for_nothing( git_iterator **out, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* tree iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value @@ -71,9 +80,7 @@ extern int git_iterator_for_nothing( extern int git_iterator_for_tree( git_iterator **out, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* index iterators will take the ignore_case value from the index; the * ignore_case flags are not used @@ -81,9 +88,7 @@ extern int git_iterator_for_tree( extern int git_iterator_for_index( git_iterator **out, git_index *index, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); extern int git_iterator_for_workdir_ext( git_iterator **out, @@ -91,9 +96,7 @@ extern int git_iterator_for_workdir_ext( const char *repo_workdir, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); /* workdir iterators will match the ignore_case value from the index of the * repository, unless you override with a non-zero flag value @@ -103,11 +106,9 @@ GIT_INLINE(int) git_iterator_for_workdir( git_repository *repo, git_index *index, git_tree *tree, - git_iterator_flag_t flags, - const char *start, - const char *end) + git_iterator_options *options) { - return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, flags, start, end); + return git_iterator_for_workdir_ext(out, repo, NULL, index, tree, options); } /* for filesystem iterators, you have to explicitly pass in the ignore_case @@ -116,9 +117,7 @@ GIT_INLINE(int) git_iterator_for_workdir( extern int git_iterator_for_filesystem( git_iterator **out, const char *root, - git_iterator_flag_t flags, - const char *start, - const char *end); + git_iterator_options *options); extern void git_iterator_free(git_iterator *iter); diff --git a/src/merge.c b/src/merge.c index 863ac8f2d11..16cd2aee080 100644 --- a/src/merge.c +++ b/src/merge.c @@ -1695,10 +1695,14 @@ int index_from_diff_list(git_index **out, git_merge_diff_list *diff_list) static git_iterator *iterator_given_or_empty(git_iterator **empty, git_iterator *given) { + git_iterator_options opts = GIT_ITERATOR_OPTIONS_INIT; + if (given) return given; - if (git_iterator_for_nothing(empty, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL) < 0) + opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if (git_iterator_for_nothing(empty, &opts) < 0) return NULL; return *empty; @@ -1780,14 +1784,17 @@ int git_merge_trees( const git_merge_options *merge_opts) { git_iterator *ancestor_iter = NULL, *our_iter = NULL, *their_iter = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor_iter, (git_tree *)ancestor_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&our_iter, (git_tree *)our_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&their_iter, (git_tree *)their_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree( + &ancestor_iter, (git_tree *)ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &our_iter, (git_tree *)our_tree, &iter_opts)) < 0 || + (error = git_iterator_for_tree( + &their_iter, (git_tree *)their_tree, &iter_opts)) < 0) goto done; error = git_merge__iterators( @@ -2319,6 +2326,7 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index git_tree *head_tree = NULL; git_index *index_repo = NULL; git_iterator *iter_repo = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *staged_diff_list = NULL, *index_diff_list = NULL; git_diff_delta *delta; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; @@ -2351,8 +2359,10 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index opts.pathspec.count = staged_paths.length; opts.pathspec.strings = (char **)staged_paths.contents; - if ((error = git_iterator_for_index(&iter_repo, index_repo, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_index(&iter_repo, index_repo, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&index_diff_list, repo, iter_repo, iter_new, &opts)) < 0) goto done; @@ -2414,6 +2424,7 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) { git_tree *head_tree = NULL; git_iterator *iter_head = NULL, *iter_new = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_diff *merged_list = NULL; git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_delta *delta; @@ -2422,9 +2433,11 @@ int git_merge__check_result(git_repository *repo, git_index *index_new) const git_index_entry *e; int error = 0; + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + if ((error = git_repository_head_tree(&head_tree, repo)) < 0 || - (error = git_iterator_for_tree(&iter_head, head_tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&iter_new, index_new, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || + (error = git_iterator_for_tree(&iter_head, head_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&iter_new, index_new, &iter_opts)) < 0 || (error = git_diff__from_iterators(&merged_list, repo, iter_head, iter_new, &opts)) < 0) goto done; diff --git a/src/notes.c b/src/notes.c index ef4b41b3164..fe8d2164f3c 100644 --- a/src/notes.c +++ b/src/notes.c @@ -663,7 +663,7 @@ int git_note_iterator_new( if (error < 0) goto cleanup; - if ((error = git_iterator_for_tree(it, tree, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_tree(it, tree, NULL)) < 0) git_iterator_free(*it); cleanup: diff --git a/src/pathspec.c b/src/pathspec.c index fab6f9a76a0..9304da7050a 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -524,16 +524,16 @@ int git_pathspec_match_workdir( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(repo); - if (!(error = git_iterator_for_workdir( - &iter, repo, NULL, NULL, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_workdir(&iter, repo, NULL, NULL, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } @@ -546,16 +546,16 @@ int git_pathspec_match_index( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(index); - if (!(error = git_iterator_for_index( - &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_index(&iter, index, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } @@ -568,16 +568,16 @@ int git_pathspec_match_tree( uint32_t flags, git_pathspec *ps) { - int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; + int error = 0; assert(tree); - if (!(error = git_iterator_for_tree( - &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { + iter_opts.flags = pathspec_match_iter_flags(flags); + if (!(error = git_iterator_for_tree(&iter, tree, &iter_opts))) { error = pathspec_match_from_iterator(out, iter, flags, ps); - git_iterator_free(iter); } diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 55d535eb4b3..1ddce464960 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -480,14 +480,16 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) int error = 0; git_buf path = GIT_BUF_INIT; git_iterator *fsit = NULL; + git_iterator_options fsit_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; if (!backend->path) /* do nothing if no path for loose refs */ return 0; + fsit_opts.flags = backend->iterator_flags; + if ((error = git_buf_printf(&path, "%s/refs", backend->path)) < 0 || - (error = git_iterator_for_filesystem( - &fsit, path.ptr, backend->iterator_flags, NULL, NULL)) < 0) { + (error = git_iterator_for_filesystem(&fsit, path.ptr, &fsit_opts)) < 0) { git_buf_free(&path); return error; } diff --git a/src/stash.c b/src/stash.c index fcb1112acc7..35824659af6 100644 --- a/src/stash.c +++ b/src/stash.c @@ -679,12 +679,14 @@ static int merge_indexes( git_index *theirs_index) { git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&theirs, theirs_index, flags, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_index(&theirs, theirs_index, &iter_opts)) < 0) goto done; error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); @@ -704,12 +706,14 @@ static int merge_index_and_tree( git_tree *theirs_tree) { git_iterator *ancestor = NULL, *ours = NULL, *theirs = NULL; - const git_iterator_flag_t flags = GIT_ITERATOR_DONT_IGNORE_CASE; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; int error; - if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_index(&ours, ours_index, flags, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&theirs, theirs_tree, flags, NULL, NULL)) < 0) + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + if ((error = git_iterator_for_tree(&ancestor, ancestor_tree, &iter_opts)) < 0 || + (error = git_iterator_for_index(&ours, ours_index, &iter_opts)) < 0 || + (error = git_iterator_for_tree(&theirs, theirs_tree, &iter_opts)) < 0) goto done; error = git_merge__iterators(out, repo, ancestor, ours, theirs, NULL); @@ -797,14 +801,15 @@ static int stage_new_files( git_tree *tree) { git_iterator *iterators[2] = { NULL, NULL }; + git_iterator_options iterator_options = GIT_ITERATOR_OPTIONS_INIT; git_index *index = NULL; int error; if ((error = git_index_new(&index)) < 0 || - (error = git_iterator_for_tree(&iterators[0], parent_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0 || - (error = git_iterator_for_tree(&iterators[1], tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)) < 0) + (error = git_iterator_for_tree( + &iterators[0], parent_tree, &iterator_options)) < 0 || + (error = git_iterator_for_tree( + &iterators[1], tree, &iterator_options)) < 0) goto done; error = git_iterator_walk(iterators, 2, stage_new_file, index); diff --git a/src/submodule.c b/src/submodule.c index 991ebc8f396..7f52c361685 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -286,7 +286,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx) git_iterator *i; const git_index_entry *entry; - if ((error = git_iterator_for_index(&i, idx, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_index(&i, idx, NULL)) < 0) return error; while (!(error = git_iterator_advance(&entry, i))) { @@ -322,7 +322,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) git_iterator *i; const git_index_entry *entry; - if ((error = git_iterator_for_tree(&i, head, 0, NULL, NULL)) < 0) + if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) return error; while (!(error = git_iterator_advance(&entry, i))) { diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c index 6011c6a9b7d..eafb1b9da43 100644 --- a/tests/diff/iterator.c +++ b/tests/diff/iterator.c @@ -30,13 +30,17 @@ static void tree_iterator_test( { 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, GIT_ITERATOR_DONT_IGNORE_CASE, start, end)); + cl_git_pass(git_iterator_for_tree(&i, t, &i_opts)); /* test loop */ while (!(error = git_iterator_advance(&entry, i))) { @@ -297,6 +301,7 @@ 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; @@ -306,8 +311,9 @@ void test_diff_iterator__tree_special_functions(void) repo, "24fa9a9fc4e202313e24b648087495441dab432b"); cl_assert(t != NULL); - cl_git_pass(git_iterator_for_tree( - &i, t, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, 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); @@ -365,11 +371,16 @@ static void index_iterator_test( 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); - cl_git_pass(git_iterator_for_index(&i, index, flags, start, end)); + iter_opts.flags = flags; + iter_opts.start = start; + iter_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, index, &iter_opts)); while (!(error = git_iterator_advance(&entry, i))) { cl_assert(entry); @@ -581,12 +592,16 @@ static void workdir_iterator_test( 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); - cl_git_pass(git_iterator_for_workdir( - &i, repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, start, end)); + 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) || @@ -765,6 +780,7 @@ 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 { @@ -796,8 +812,12 @@ void test_diff_iterator__workdir_builtin_ignores(void) 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, GIT_ITERATOR_DONT_AUTOEXPAND, "dir", "sub/sub/file")); + &i, repo, NULL, NULL, &i_opts)); cl_git_pass(git_iterator_current(&entry, i)); for (idx = 0; entry != NULL; ++idx) { @@ -827,12 +847,17 @@ 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, GIT_ITERATOR_IGNORE_CASE, start, end)); + &i, repo, NULL, NULL, &i_opts)); cl_git_pass(git_iterator_current(&entry, i)); for (idx = 0; entry != NULL; ++idx) { @@ -877,14 +902,16 @@ static void check_tree_range( { 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, - ignore_case ? GIT_ITERATOR_IGNORE_CASE : GIT_ITERATOR_DONT_IGNORE_CASE, - start, end)); + cl_git_pass(git_iterator_for_tree(&i, head, &i_opts)); for (count = 0; !(error = git_iterator_advance(NULL, i)); ++count) /* count em up */; @@ -931,6 +958,7 @@ static void check_index_range( { git_index *index; git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; int error, count, caps; bool is_ignoring_case; @@ -942,7 +970,11 @@ static void check_index_range( if (ignore_case != is_ignoring_case) cl_git_pass(git_index_set_caps(index, caps ^ GIT_INDEXCAP_IGNORE_CASE)); - cl_git_pass(git_iterator_for_index(&i, index, 0, start, end)); + i_opts.flags = 0; + i_opts.start = start; + i_opts.end = end; + + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); cl_assert(git_iterator_ignore_case(i) == ignore_case); diff --git a/tests/merge/trees/treediff.c b/tests/merge/trees/treediff.c index b96c4c4dbed..f21d99b6d2d 100644 --- a/tests/merge/trees/treediff.c +++ b/tests/merge/trees/treediff.c @@ -44,6 +44,7 @@ static void test_find_differences( git_oid ancestor_oid, ours_oid, theirs_oid; git_tree *ancestor_tree, *ours_tree, *theirs_tree; git_iterator *ancestor_iter, *ours_iter, *theirs_iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; git_merge_options opts = GIT_MERGE_OPTIONS_INIT; opts.tree_flags |= GIT_MERGE_TREE_FIND_RENAMES; @@ -67,12 +68,11 @@ static void test_find_differences( cl_git_pass(git_tree_lookup(&ours_tree, repo, &ours_oid)); cl_git_pass(git_tree_lookup(&theirs_tree, repo, &theirs_oid)); - cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); - cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); - cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, - GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + + cl_git_pass(git_iterator_for_tree(&ancestor_iter, ancestor_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&ours_iter, ours_tree, &iter_opts)); + cl_git_pass(git_iterator_for_tree(&theirs_iter, theirs_tree, &iter_opts)); cl_git_pass(git_merge_diff_list__find_differences(merge_diff_list, ancestor_iter, ours_iter, theirs_iter)); cl_git_pass(git_merge_diff_list__find_renames(repo, merge_diff_list, &opts)); diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index bb2d3a1867b..4d0d12be1c5 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -126,6 +126,7 @@ static void expect_iterator_items( 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"); @@ -133,19 +134,19 @@ void test_repo_iterator__index(void) cl_git_pass(git_repository_index(&index, g_repo)); /* autoexpand with no tree entries for index */ - cl_git_pass(git_iterator_for_index(&i, index, 0, NULL, NULL)); + cl_git_pass(git_iterator_for_index(&i, index, NULL)); expect_iterator_items(i, 20, NULL, 20, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 22, NULL, 22, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 12, NULL, 22, NULL); git_iterator_free(i); @@ -155,6 +156,7 @@ void test_repo_iterator__index(void) void test_repo_iterator__index_icase(void) { git_iterator *i; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; git_index *index; int caps; @@ -167,32 +169,45 @@ void test_repo_iterator__index_icase(void) cl_git_pass(git_index_set_caps(index, caps & ~GIT_INDEXCAP_IGNORE_CASE)); /* autoexpand with no tree entries over range */ - cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D")); + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 7, NULL, 7, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 3, NULL, 3, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 8, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 4, NULL, 4, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 5, NULL, 8, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 1, NULL, 4, NULL); git_iterator_free(i); @@ -200,33 +215,47 @@ void test_repo_iterator__index_icase(void) cl_git_pass(git_index_set_caps(index, caps | GIT_INDEXCAP_IGNORE_CASE)); /* autoexpand with no tree entries over range */ - cl_git_pass(git_iterator_for_index(&i, index, 0, "c", "k/D")); + i_opts.flags = 0; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 13, NULL, 13, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index(&i, index, 0, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 5, NULL, 5, NULL); git_iterator_free(i); /* auto expand with tree entries */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_INCLUDE_TREES; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 14, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 6, NULL, 6, NULL); git_iterator_free(i); /* no auto expand (implies trees included) */ - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + i_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 9, NULL, 14, NULL); git_iterator_free(i); - cl_git_pass(git_iterator_for_index( - &i, index, GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + i_opts.start = "k"; + i_opts.end = "k/Z"; + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); expect_iterator_items(i, 1, NULL, 6, NULL); git_iterator_free(i); @@ -237,6 +266,7 @@ void test_repo_iterator__index_icase(void) 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"); @@ -244,19 +274,21 @@ void test_repo_iterator__tree(void) 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, 0, NULL, NULL)); + 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 */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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) */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + 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); @@ -267,75 +299,98 @@ void test_repo_iterator__tree_icase(void) { git_iterator *i; git_tree *head; - git_iterator_flag_t flag; + 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)); - flag = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z")); + 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 */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + 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) */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + 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); - flag = GIT_ITERATOR_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_tree(&i, head, flag, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree(&i, head, flag, "k", "k/Z")); + 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 */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + 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) */ - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_tree( - &i, head, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + 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); @@ -345,6 +400,7 @@ void test_repo_iterator__tree_icase(void) 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", @@ -396,19 +452,21 @@ void test_repo_iterator__tree_more(void) 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, 0, NULL, NULL)); + 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 */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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) */ - cl_git_pass(git_iterator_for_tree( - &i, head, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + 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); @@ -463,6 +521,8 @@ void test_repo_iterator__tree_case_conflicts_0(void) 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[] = { @@ -486,25 +546,23 @@ void test_repo_iterator__tree_case_conflicts_0(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); @@ -517,6 +575,8 @@ void test_repo_iterator__tree_case_conflicts_1(void) 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[] = { @@ -541,25 +601,23 @@ void test_repo_iterator__tree_case_conflicts_1(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); @@ -572,6 +630,8 @@ void test_repo_iterator__tree_case_conflicts_2(void) 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", @@ -639,19 +699,18 @@ void test_repo_iterator__tree_case_conflicts_2(void) cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id)); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_DONT_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_tree( - &i, tree, GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); @@ -661,23 +720,24 @@ void test_repo_iterator__tree_case_conflicts_2(void) 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, 0, NULL, NULL)); + 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 */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + 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); } @@ -685,73 +745,97 @@ void test_repo_iterator__workdir(void) void test_repo_iterator__workdir_icase(void) { git_iterator *i; - git_iterator_flag_t flag; + git_iterator_options i_opts = GIT_ITERATOR_OPTIONS_INIT; g_repo = cl_git_sandbox_init("icase"); - flag = GIT_ITERATOR_DONT_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "k", "k/Z")); + 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 */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + 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) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + 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); - flag = GIT_ITERATOR_IGNORE_CASE; - /* auto expand with no tree entries */ - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir(&i, g_repo, NULL, NULL, flag, "k", "k/Z")); + 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 */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_INCLUDE_TREES, "k", "k/Z")); + 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) */ - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "c", "k/D")); + 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); - cl_git_pass(git_iterator_for_workdir( - &i, g_repo, NULL, NULL, flag | GIT_ITERATOR_DONT_AUTOEXPAND, "k", "k/Z")); + 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); } @@ -796,6 +880,7 @@ static void build_workdir_tree(const char *root, int dirs, int subs) 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"); @@ -804,13 +889,13 @@ void test_repo_iterator__workdir_depth(void) 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, 0, NULL, NULL)); + 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) */ - cl_git_pass(git_iterator_for_workdir( - &iter, g_repo, NULL, NULL, GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); } @@ -818,6 +903,8 @@ void test_repo_iterator__workdir_depth(void) 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", @@ -863,18 +950,17 @@ void test_repo_iterator__fs(void) build_workdir_tree("status/subdir", 2, 4); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", 0, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + 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); @@ -882,20 +968,18 @@ void test_repo_iterator__fs(void) git__tsort((void **)expect_trees, 18, (git__tsort_cmp)git__strcasecmp); git__tsort((void **)expect_noauto, 5, (git__tsort_cmp)git__strcasecmp); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + 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); - cl_git_pass(git_iterator_for_filesystem( - &i, "status/subdir", GIT_ITERATOR_IGNORE_CASE | - GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + 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); } @@ -923,7 +1007,7 @@ void test_repo_iterator__fs2(void) g_repo = cl_git_sandbox_init("testrepo"); cl_git_pass(git_iterator_for_filesystem( - &i, "testrepo/.git/refs", 0, NULL, NULL)); + &i, "testrepo/.git/refs", NULL)); expect_iterator_items(i, 13, expect_base, 13, expect_base); git_iterator_free(i); } @@ -947,7 +1031,7 @@ void test_repo_iterator__unreadable_dir(void) cl_git_mkfile("empty_standard_repo/r/d", "final"); cl_git_pass(git_iterator_for_filesystem( - &i, "empty_standard_repo/r", 0, NULL, NULL)); + &i, "empty_standard_repo/r", NULL)); cl_git_pass(git_iterator_advance(&e, i)); /* a */ cl_git_fail(git_iterator_advance(&e, i)); /* b */ diff --git a/tests/submodule/status.c b/tests/submodule/status.c index 6721ee92a91..5f4e620531c 100644 --- a/tests/submodule/status.c +++ b/tests/submodule/status.c @@ -264,6 +264,7 @@ static int confirm_submodule_status( void test_submodule_status__iterator(void) { git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry; size_t i; static const char *expected[] = { @@ -308,9 +309,10 @@ void test_submodule_status__iterator(void) git_status_options opts = GIT_STATUS_OPTIONS_INIT; git_index *index; + iter_opts.flags = GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES; + cl_git_pass(git_repository_index(&index, g_repo)); - cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, - GIT_ITERATOR_IGNORE_CASE | GIT_ITERATOR_INCLUDE_TREES, NULL, NULL)); + cl_git_pass(git_iterator_for_workdir(&iter, g_repo, index, NULL, &iter_opts)); for (i = 0; !git_iterator_advance(&entry, iter); ++i) cl_assert_equal_s(expected[i], entry->path); diff --git a/tests/threads/iterator.c b/tests/threads/iterator.c index 8a2d79c2e56..6b86cf1a045 100644 --- a/tests/threads/iterator.c +++ b/tests/threads/iterator.c @@ -13,10 +13,13 @@ static void *run_workdir_iterator(void *arg) { int error = 0; git_iterator *iter; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; const git_index_entry *entry = NULL; + iter_opts.flags = GIT_ITERATOR_DONT_AUTOEXPAND; + cl_git_pass(git_iterator_for_workdir( - &iter, _repo, NULL, NULL, GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + &iter, _repo, NULL, NULL, &iter_opts)); while (!error) { if (entry && entry->mode == GIT_FILEMODE_TREE) { From ef206124de957408c8d867e2f923d0611a7274fc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 28 Jul 2015 19:55:37 -0500 Subject: [PATCH 011/101] Move filelist into the iterator handling itself. --- include/git2/diff.h | 7 ++ src/diff.c | 46 ++++++-- src/iterator.c | 115 ++++++++++++++++++-- src/iterator.h | 7 ++ src/merge.c | 5 +- tests/repo/iterator.c | 248 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 405 insertions(+), 23 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 0abbc7f06f8..c3589bb13a4 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -163,6 +163,13 @@ typedef enum { /** Include unreadable files in the diff */ GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17), + /** Use literal path matching in the iterators. This is more broad + * than the DISABLE_PATHSPEC_MATCH flag. The caller must provide an + * array of paths (no patterns or prefixes). Only values included in + * that list will be returned. + */ + GIT_DIFF_ENABLE_FILELIST_MATCH = (1u << 18), + /* * Options controlling how output will be generated */ diff --git a/src/diff.c b/src/diff.c index 58004db2105..d87738fb38a 100644 --- a/src/diff.c +++ b/src/diff.c @@ -74,6 +74,24 @@ static int diff_insert_delta( return error; } +static bool diff_pathspec_match( + const char **matched_pathspec, git_diff *diff, const char *path) +{ + /* The iterator has filtered out paths for us, so the fact that we're + * seeing this patch means that it must match the given path list. + */ + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_ENABLE_FILELIST_MATCH)) { + *matched_pathspec = path; + return true; + } + + return git_pathspec__match( + &diff->pathspec, path, + DIFF_FLAG_IS_SET(diff, GIT_DIFF_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, @@ -110,11 +128,7 @@ static int diff_delta__from_one( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) return 0; - if (!git_pathspec__match( - &diff->pathspec, entry->path, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - &matched_pathspec, NULL)) + if (!diff_pathspec_match(&matched_pathspec, diff, entry->path)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -755,11 +769,7 @@ static int maybe_modified( const char *matched_pathspec; int error = 0; - if (!git_pathspec__match( - &diff->pathspec, oitem->path, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), - DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), - &matched_pathspec, NULL)) + if (!diff_pathspec_match(&matched_pathspec, diff, oitem->path)) return 0; memset(&noid, 0, sizeof(noid)); @@ -1266,7 +1276,9 @@ int git_diff__from_iterators( #define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ git_iterator *a = NULL, *b = NULL; \ - char *pfx = opts ? git_pathspec_prefix(&opts->pathspec) : NULL; \ + git_vector pathlist = GIT_VECTOR_INIT; \ + char *pfx = (opts && !(opts->flags & GIT_DIFF_ENABLE_FILELIST_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; \ @@ -1276,9 +1288,19 @@ int git_diff__from_iterators( b_opts.start = pfx; \ b_opts.end = pfx; \ GITERR_CHECK_VERSION(opts, GIT_DIFF_OPTIONS_VERSION, "git_diff_options"); \ - if (!(error = MAKE_FIRST) && !(error = MAKE_SECOND)) \ + if (opts && (opts->flags & GIT_DIFF_ENABLE_FILELIST_MATCH) && opts->pathspec.count) { \ + size_t __i; \ + error = git_vector_init(&pathlist, opts->pathspec.count, NULL); \ + for (__i = 0; !error && __i < opts->pathspec.count; __i++) { \ + error = git_vector_insert(&pathlist, opts->pathspec.strings[__i]); \ + } \ + a_opts.pathlist = &pathlist; \ + b_opts.pathlist = &pathlist; \ + } \ + 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); \ + git_vector_free(&pathlist); \ } while (0) int git_diff_tree_to_tree( diff --git a/src/iterator.c b/src/iterator.c index 374caf96d1d..28629c7082b 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -42,6 +42,8 @@ (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) \ + (P)->base.pathlist = options->pathlist; \ } while (0) #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) @@ -106,6 +108,12 @@ static int iterator__update_ignore_case( iter->prefixcomp = iterator__ignore_case(iter) ? git__prefixcmp_icase : git__prefixcmp; + if (iter->pathlist) { + git_vector_set_cmp(iter->pathlist, iterator__ignore_case(iter) ? + git__strcasecmp : git__strcmp); + git_vector_sort(iter->pathlist); + } + return error; } @@ -616,6 +624,9 @@ int git_iterator_for_tree( if (tree == NULL) return git_iterator_for_nothing(iter, options); + /* not yet supported */ + assert (!options || !options->pathlist); + if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0) return error; @@ -668,15 +679,47 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii) return ie; } -static const git_index_entry *index_iterator__advance_over_conflicts(index_iterator *ii) +static const git_index_entry *index_iterator__advance_over_unwanted(index_iterator *ii) { const git_index_entry *ie = index_iterator__index_entry(ii); + const char *p; + int cmp; - if (!iterator__include_conflicts(ii)) { - while (ie && git_index_entry_is_conflict(ie)) { + while (ie) { + if (!iterator__include_conflicts(ii) && + git_index_entry_is_conflict(ie)) { ii->current++; ie = index_iterator__index_entry(ii); + continue; } + + /* if we have a pathlist, this entry's path must be in it to be + * returned. otherwise, advance the pathlist entry or the iterator + * until we find the next path that we want to return. + */ + if (ii->base.pathlist) { + if (ii->base.pathlist_idx >= ii->base.pathlist->length) { + ii->current = SIZE_MAX; + ie = NULL; + break; + } + + p = ii->base.pathlist->contents[ii->base.pathlist_idx]; + cmp = ii->base.pathlist->_cmp(p, ie->path); + + if (cmp < 0) { + ii->base.pathlist_idx++; + continue; + } + + if (cmp > 0) { + ii->current++; + ie = index_iterator__index_entry(ii); + continue; + } + } + + break; } return ie; @@ -705,7 +748,7 @@ static void index_iterator__next_prefix_tree(index_iterator *ii) static int index_iterator__first_prefix_tree(index_iterator *ii) { - const git_index_entry *ie = index_iterator__advance_over_conflicts(ii); + const git_index_entry *ie = index_iterator__advance_over_unwanted(ii); const char *scan, *prior, *slash; if (!ie || !iterator__include_trees(ii)) @@ -818,17 +861,22 @@ static int index_iterator__reset( { index_iterator *ii = (index_iterator *)self; const git_index_entry *ie; + size_t pathlist_idx = 0; if (iterator__reset_range(self, start, end) < 0) return -1; ii->current = 0; + ii->base.pathlist_idx = 0; + /* 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. + */ if (ii->base.start) git_index_snapshot_find( &ii->current, &ii->entries, ii->entry_srch, ii->base.start, 0, 0); - if ((ie = index_iterator__advance_over_conflicts(ii)) == NULL) + if ((ie = index_iterator__advance_over_unwanted(ii)) == NULL) return 0; if (git_buf_sets(&ii->partial, ie->path) < 0) @@ -1004,25 +1052,60 @@ static void fs_iterator__seek_frame_start( ff->index = 0; } +typedef enum { + DIRLOAD_PATHLIST_NONE = 0, + DIRLOAD_PATHLIST_EXACT = 1, + DIRLOAD_PATHLIST_DIRECTORY = 2, +} dirload_pathlist_match_t; + +static dirload_pathlist_match_t dirload_pathlist_match( + git_vector *pathlist, + const char *path, + size_t path_len, + int (*prefixcomp)(const char *a, const char *b)) +{ + const char *matched; + size_t idx; + + if (git_vector_bsearch2( + &idx, pathlist, pathlist->_cmp, path) != GIT_ENOTFOUND) + return DIRLOAD_PATHLIST_EXACT; + + /* the explicit path we searched for was not found, but this may be + * a directory and the pathlist contains a file in it. check. + */ + if ((matched = git_vector_get(pathlist, idx)) != NULL && + prefixcomp(matched, path) == 0 && + matched[path_len] == '/') + return DIRLOAD_PATHLIST_DIRECTORY; + + return DIRLOAD_PATHLIST_NONE; +} + static int dirload_with_stat( + git_vector *contents, const char *dirpath, size_t prefix_len, unsigned int flags, const char *start_stat, const char *end_stat, - git_vector *contents) + git_vector *pathlist) { git_path_diriter diriter = GIT_PATH_DIRITER_INIT; const char *path; int (*strncomp)(const char *a, const char *b, size_t sz); + int (*prefixcomp)(const char *a, const char *b); size_t start_len = start_stat ? strlen(start_stat) : 0; size_t end_len = end_stat ? strlen(end_stat) : 0; fs_iterator_path_with_stat *ps; size_t path_len, cmp_len, ps_size; + dirload_pathlist_match_t pathlist_match = DIRLOAD_PATHLIST_EXACT; int error; strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? git__strncasecmp : git__strncmp; + prefixcomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? + git__prefixcmp_icase : git__prefixcmp; /* Any error here is equivalent to the dir not existing, skip over it */ if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0) { @@ -1044,10 +1127,20 @@ static int dirload_with_stat( cmp_len = min(start_len, path_len); if (cmp_len && strncomp(path, start_stat, cmp_len) < 0) continue; + /* skip if after end_stat */ cmp_len = min(end_len, path_len); if (cmp_len && strncomp(path, end_stat, cmp_len) > 0) continue; + /* skip if we have a pathlist and this isn't in it. note that we + * haven't stat'd yet to know if it's a file or a directory, so this + * match for files like `foo` when we're looking for `foo/bar` + */ + if (pathlist && + !(pathlist_match = dirload_pathlist_match( + pathlist, path, path_len, prefixcomp))) + continue; + /* Make sure to append two bytes, one for the path's null * termination, one for a possible trailing '/' for folders. */ @@ -1068,6 +1161,12 @@ static int dirload_with_stat( continue; } + if (pathlist_match == DIRLOAD_PATHLIST_DIRECTORY) { + /* were looking for a directory, but this is a file */ + git__free(ps); + continue; + } + /* 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; @@ -1113,9 +1212,9 @@ static int fs_iterator__expand_dir(fs_iterator *fi) ff = fs_iterator__alloc_frame(fi); GITERR_CHECK_ALLOC(ff); - error = dirload_with_stat( + error = dirload_with_stat(&ff->entries, fi->path.ptr, fi->root_len, fi->dirload_flags, - fi->base.start, fi->base.end, &ff->entries); + fi->base.start, fi->base.end, fi->base.pathlist); if (error < 0) { git_error_state last_error = { 0 }; diff --git a/src/iterator.h b/src/iterator.h index 46e96f0448a..0ea2bc05365 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -43,6 +43,11 @@ typedef struct { const char *start; const char *end; + /* paths to include in the iterator (literal). any paths not listed + * will be excluded. note that this vector may be resorted! + */ + git_vector *pathlist; + /* flags, from above */ unsigned int flags; } git_iterator_options; @@ -65,6 +70,8 @@ struct git_iterator { git_repository *repo; char *start; char *end; + git_vector *pathlist; + size_t pathlist_idx; int (*prefixcomp)(const char *str, const char *prefix); size_t stat_calls; unsigned int flags; diff --git a/src/merge.c b/src/merge.c index 16cd2aee080..1460a50403f 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2356,10 +2356,8 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index goto done; } - opts.pathspec.count = staged_paths.length; - opts.pathspec.strings = (char **)staged_paths.contents; - iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + iter_opts.pathlist = &staged_paths; if ((error = git_iterator_for_index(&iter_repo, index_repo, &iter_opts)) < 0 || (error = git_iterator_for_index(&iter_new, index_new, &iter_opts)) < 0 || @@ -2406,6 +2404,7 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde * will be applied by the merge (including conflicts). Ensure that there * are no changes in the workdir to these paths. */ + opts.flags |= GIT_DIFF_ENABLE_FILELIST_MATCH; opts.pathspec.count = merged_paths->length; opts.pathspec.strings = (char **)merged_paths->contents; diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 4d0d12be1c5..8af7bc5331d 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -1073,3 +1073,251 @@ void test_repo_iterator__skips_fifos_and_such(void) 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 = &filelist; + + /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ + + cl_git_pass(git_iterator_for_index(&i, 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, 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, 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; + + 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/a")); + + i_opts.pathlist = &filelist; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); + expect_iterator_items(i, 4, NULL, 4, 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 = &filelist; + + i_opts.start = "c"; + i_opts.end = "k/D"; + cl_git_pass(git_iterator_for_index(&i, 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, 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, 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, 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/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"); + + /* 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 = &filelist; + + 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/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"); + + i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; + i_opts.pathlist = &filelist; + + 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); +} From 810cabb9dfa57127e78cb47dbe8ea95943fb2642 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 28 Jul 2015 20:04:11 -0500 Subject: [PATCH 012/101] racy-git: TODO to use improved diffing --- src/index.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.c b/src/index.c index a6a62e3270a..53120e49c06 100644 --- a/src/index.c +++ b/src/index.c @@ -728,6 +728,7 @@ static int truncate_racily_clean(git_index *index) if (!is_racy_timestamp(ts, entry)) continue; + /* TODO: use the (non-fnmatching) filelist iterator */ diff_opts.pathspec.count = 1; diff_opts.pathspec.strings = (char **) &entry->path; From 6c9352bf30e97af5d646d92ceab1c7b0f4c7a1c4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Aug 2015 18:30:39 -0400 Subject: [PATCH 013/101] iterator: sort subdirs properly with pathlist When given a pathlist, don't assume that directories sort before files. Walk through any list of entries sorting before us to make sure that we've exhausted all entries that *aren't* directories. Eg, if we're searching for 'foo/bar', and we have a 'foo.c', keep advancing the pathlist to keep looking for an entry prefixed with 'foo/'. --- src/iterator.c | 21 +++++++++++++++------ tests/repo/iterator.c | 8 +++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index 28629c7082b..9fa7cab2ca2 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1071,13 +1071,22 @@ static dirload_pathlist_match_t dirload_pathlist_match( &idx, pathlist, pathlist->_cmp, path) != GIT_ENOTFOUND) return DIRLOAD_PATHLIST_EXACT; - /* the explicit path we searched for was not found, but this may be - * a directory and the pathlist contains a file in it. check. + /* the explicit path that we've seen in the directory iterator was + * not found - however, we may have hit a subdirectory in the directory + * iterator. examine the pathlist to see if it contains children of the + * current path. if so, indicate that we've found a subdirectory that + * is worth examining. */ - if ((matched = git_vector_get(pathlist, idx)) != NULL && - prefixcomp(matched, path) == 0 && - matched[path_len] == '/') - return DIRLOAD_PATHLIST_DIRECTORY; + while ((matched = git_vector_get(pathlist, idx)) != NULL && + prefixcomp(matched, path) == 0) { + + if (matched[path_len] == '/') + return DIRLOAD_PATHLIST_DIRECTORY; + else if (matched[path_len] > '/') + break; + + idx++; + } return DIRLOAD_PATHLIST_NONE; } diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 8af7bc5331d..84dfbe113fe 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -26,7 +26,7 @@ static void expect_iterator_items( const git_index_entry *entry; int count, error; int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); - bool v = false; + bool v = true; if (expected_flat < 0) { v = true; expected_flat = -expected_flat; } if (expected_total < 0) { v = true; expected_total = -expected_total; } @@ -1236,8 +1236,11 @@ void test_repo_iterator__workdirfilelist(void) 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"); @@ -1284,8 +1287,11 @@ void test_repo_iterator__workdirfilelist_icase(void) 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"); From 3273ab3f0b04d673b9515b149674d5716939d9a5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 28 Aug 2015 20:06:18 -0400 Subject: [PATCH 014/101] diff: better document GIT_DIFF_PATHSPEC_DISABLE Document that `GIT_DIFF_PATHSPEC_DISABLE` is not necessarily about explicit path matching, but also includes matching of directory names. Enforce this in a test. --- include/git2/diff.h | 4 +- tests/diff/workdir.c | 210 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index c3589bb13a4..d3adf9f0164 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -130,7 +130,9 @@ typedef enum { GIT_DIFF_INCLUDE_CASECHANGE = (1u << 11), /** If the pathspec is set in the diff options, this flags means to - * apply it as an exact match instead of as an fnmatch pattern. + * use exact prefix matches instead of an fnmatch pattern. Each + * path in the list must either be a full filename or a subdirectory + * prefix. */ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12), diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 8a23f53ae7d..503d674502a 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -444,6 +444,216 @@ void test_diff_workdir__to_index_with_pathspec(void) git_diff_free(diff); } +void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff *diff = NULL; + diff_expects exp; + char *pathspec = NULL; + int use_iterator; + + g_repo = cl_git_sandbox_init("status"); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED | + GIT_DIFF_DISABLE_PATHSPEC_MATCH; + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 0; + + /* ensure that an empty pathspec list is ignored */ + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that a single NULL pathspec is filtered out (like when using + * fnmatch filtering) + */ + opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(13, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(4, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + pathspec = "modified_file"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified */ + pathspec = "subdir"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that subdirs can be specified with a trailing slash */ + pathspec = "subdir/"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching is completely disabled */ + pathspec = "subdir/*"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that the prefix matching isn't completely braindead */ + pathspec = "subdi"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + + /* ensure that fnmatching isn't working at all */ + pathspec = "*_deleted"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); +} + void test_diff_workdir__filemode_changes(void) { git_diff *diff = NULL; From 91c9484c2302aedd38e83b1343cbefeb1685223d Mon Sep 17 00:00:00 2001 From: Ben Chatelain Date: Sat, 29 Aug 2015 17:46:34 -0600 Subject: [PATCH 015/101] Escape @ in doc comment --- include/git2/cred_helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h index 840c2253696..1416d5642a1 100644 --- a/include/git2/cred_helpers.h +++ b/include/git2/cred_helpers.h @@ -34,7 +34,7 @@ typedef struct git_cred_userpass_payload { * * @param cred The newly created credential object. * @param url The resource for which we are demanding a credential. - * @param user_from_url The username that was embedded in a "user@host" + * @param user_from_url The username that was embedded in a "user\@host" * remote url, or NULL if not included. * @param allowed_types A bitmask stating which cred types are OK to return. * @param payload The payload provided when specifying this callback. (This is From 4a0dbeb0d35343ded24b51906f2a8f8ef6c7910b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 17:06:26 -0400 Subject: [PATCH 016/101] diff: use new iterator pathlist handling When using literal pathspecs in diff with `GIT_DIFF_DISABLE_PATHSPEC_MATCH` turn on the faster iterator pathlist handling. Updates iterator pathspecs to include directory prefixes (eg, `foo/`) for compatibility with `GIT_DIFF_DISABLE_PATHSPEC_MATCH`. --- src/diff.c | 28 ++-- src/iterator.c | 292 +++++++++++++++++++++++++++--------------- src/iterator.h | 15 ++- src/merge.c | 3 +- tests/diff/workdir.c | 26 +++- tests/repo/iterator.c | 17 ++- 6 files changed, 246 insertions(+), 135 deletions(-) diff --git a/src/diff.c b/src/diff.c index d87738fb38a..32c1d4aa5ae 100644 --- a/src/diff.c +++ b/src/diff.c @@ -80,14 +80,13 @@ static bool diff_pathspec_match( /* The iterator has filtered out paths for us, so the fact that we're * seeing this patch means that it must match the given path list. */ - if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_ENABLE_FILELIST_MATCH)) { + if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { *matched_pathspec = path; return true; } return git_pathspec__match( - &diff->pathspec, path, - DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), + &diff->pathspec, path, false, DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), matched_pathspec, NULL); } @@ -1063,6 +1062,12 @@ static int handle_unmatched_new_item( &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) { @@ -1276,8 +1281,7 @@ int git_diff__from_iterators( #define DIFF_FROM_ITERATORS(MAKE_FIRST, FLAGS_FIRST, MAKE_SECOND, FLAGS_SECOND) do { \ git_iterator *a = NULL, *b = NULL; \ - git_vector pathlist = GIT_VECTOR_INIT; \ - char *pfx = (opts && !(opts->flags & GIT_DIFF_ENABLE_FILELIST_MATCH)) ? \ + 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; \ @@ -1288,19 +1292,15 @@ int git_diff__from_iterators( 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_ENABLE_FILELIST_MATCH) && opts->pathspec.count) { \ - size_t __i; \ - error = git_vector_init(&pathlist, opts->pathspec.count, NULL); \ - for (__i = 0; !error && __i < opts->pathspec.count; __i++) { \ - error = git_vector_insert(&pathlist, opts->pathspec.strings[__i]); \ - } \ - a_opts.pathlist = &pathlist; \ - b_opts.pathlist = &pathlist; \ + 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); \ - git_vector_free(&pathlist); \ } while (0) int git_diff_tree_to_tree( diff --git a/src/iterator.c b/src/iterator.c index 9fa7cab2ca2..bad24d13ee6 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -38,12 +38,15 @@ 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) \ - (P)->base.pathlist = options->pathlist; \ + if (options && options->pathlist.count && \ + iterator_pathlist__init(&P->base, &options->pathlist) < 0) { \ + git__free(P); return -1; } \ } while (0) #define iterator__flag(I,F) ((((git_iterator *)(I))->flags & GIT_ITERATOR_ ## F) != 0) @@ -61,6 +64,82 @@ (iterator__end(I) && ((git_iterator *)(I))->prefixcomp((PATH),iterator__end(I)) > 0) +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) +{ + size_t i; + + if (git_vector_init(&iter->pathlist, pathspec->count, iter->strcomp) < 0) + return -1; + + for (i = 0; i < pathspec->count; i++) { + if (!pathspec->strings[i]) + continue; + + if (git_vector_insert(&iter->pathlist, pathspec->strings[i]) < 0) + return -1; + } + + git_vector_sort(&iter->pathlist); + + return 0; +} + +static iterator_pathlist__match_t iterator_pathlist__match( + git_iterator *iter, const char *path, size_t path_len) +{ + const char *p; + size_t idx; + int error; + + error = git_vector_bsearch2(&idx, &iter->pathlist, iter->strcomp, path); + + if (error == 0) + return ITERATOR_PATHLIST_MATCH; + + /* 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. + */ + while ((p = git_vector_get(&iter->pathlist, idx)) != NULL) { + if (iter->prefixcomp(p, path) != 0) + break; + + /* an exact match would have been matched by the bsearch above */ + assert(p[path_len]); + + /* is this a literal directory entry (eg `foo/`) or a file beneath */ + if (p[path_len] == '/') { + while (p[path_len] == '/') + path_len++; + + return (p[path_len] == '\0') ? + ITERATOR_PATHLIST_MATCH_DIRECTORY : + ITERATOR_PATHLIST_MATCH_CHILD; + } + + if (p[path_len] > '/') + break; + + idx++; + } + + return ITERATOR_PATHLIST_NONE; +} + +static void iterator_pathlist__update_ignore_case(git_iterator *iter) +{ + git_vector_set_cmp(&iter->pathlist, iter->strcomp); + git_vector_sort(&iter->pathlist); +} + + static int iterator__reset_range( git_iterator *iter, const char *start, const char *end) { @@ -87,7 +166,8 @@ static int iterator__update_ignore_case( git_iterator *iter, git_iterator_flag_t flags) { - int error = 0, ignore_case = -1; + bool ignore_case; + int error; if ((flags & GIT_ITERATOR_IGNORE_CASE) != 0) ignore_case = true; @@ -96,25 +176,29 @@ static int iterator__update_ignore_case( else { git_index *index; - if (!(error = git_repository_index__weakptr(&index, iter->repo))) - ignore_case = (index->ignore_case != false); + if ((error = git_repository_index__weakptr(&index, iter->repo)) < 0) + return error; + + ignore_case = (index->ignore_case == 1); } - if (ignore_case > 0) + if (ignore_case) { iter->flags = (iter->flags | GIT_ITERATOR_IGNORE_CASE); - else if (ignore_case == 0) - iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); - iter->prefixcomp = iterator__ignore_case(iter) ? - git__prefixcmp_icase : git__prefixcmp; + iter->strcomp = git__strcasecmp; + iter->strncomp = git__strncasecmp; + iter->prefixcomp = git__prefixcmp_icase; + } else { + iter->flags = (iter->flags & ~GIT_ITERATOR_IGNORE_CASE); - if (iter->pathlist) { - git_vector_set_cmp(iter->pathlist, iterator__ignore_case(iter) ? - git__strcasecmp : git__strcmp); - git_vector_sort(iter->pathlist); + iter->strcomp = git__strcmp; + iter->strncomp = git__strncmp; + iter->prefixcomp = git__prefixcmp; } - return error; + iterator_pathlist__update_ignore_case(iter); + + return 0; } GIT_INLINE(void) iterator__clear_entry(const git_index_entry **entry) @@ -210,7 +294,6 @@ typedef struct { int path_ambiguities; bool path_has_filename; bool entry_is_current; - int (*strncomp)(const char *a, const char *b, size_t sz); } tree_iterator; static char *tree_iterator__current_filename( @@ -280,7 +363,7 @@ static int tree_iterator__search_cmp(const void *key, const void *val, void *p) return git_path_cmp( tf->start, tf->startlen, false, te->filename, te->filename_len, te->attr == GIT_FILEMODE_TREE, - ((tree_iterator *)p)->strncomp); + ((git_iterator *)p)->strncomp); } static bool tree_iterator__move_to_next( @@ -312,7 +395,7 @@ static int tree_iterator__set_next(tree_iterator *ti, tree_iterator_frame *tf) for (; tf->next < tf->n_entries; tf->next++, last = te) { te = tf->entries[tf->next]->te; - if (last && tree_iterator__te_cmp(last, te, ti->strncomp)) + if (last && tree_iterator__te_cmp(last, te, ti->base.strncomp)) break; /* try to load trees for items in [current,next) range */ @@ -624,9 +707,6 @@ int git_iterator_for_tree( if (tree == NULL) return git_iterator_for_nothing(iter, options); - /* not yet supported */ - assert (!options || !options->pathlist); - if ((error = git_object_dup((git_object **)&tree, (git_object *)tree)) < 0) return error; @@ -637,7 +717,6 @@ int git_iterator_for_tree( if ((error = iterator__update_ignore_case((git_iterator *)ti, options ? options->flags : 0)) < 0) goto fail; - ti->strncomp = iterator__ignore_case(ti) ? git__strncasecmp : git__strncmp; if ((error = git_pool_init(&ti->pool, sizeof(tree_iterator_entry),0)) < 0 || (error = tree_iterator__create_root_frame(ti, tree)) < 0 || @@ -660,6 +739,8 @@ typedef struct { 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; @@ -679,10 +760,12 @@ static const git_index_entry *index_iterator__index_entry(index_iterator *ii) return ie; } -static const git_index_entry *index_iterator__advance_over_unwanted(index_iterator *ii) +static const git_index_entry *index_iterator__advance_over_unwanted( + index_iterator *ii) { const git_index_entry *ie = index_iterator__index_entry(ii); const char *p; + size_t p_len; int cmp; while (ie) { @@ -697,24 +780,48 @@ static const git_index_entry *index_iterator__advance_over_unwanted(index_iterat * returned. otherwise, advance the pathlist entry or the iterator * until we find the next path that we want to return. */ - if (ii->base.pathlist) { - if (ii->base.pathlist_idx >= ii->base.pathlist->length) { + if (ii->base.pathlist.length) { + + if (ii->pathlist_idx >= ii->base.pathlist.length) { ii->current = SIZE_MAX; ie = NULL; break; } - p = ii->base.pathlist->contents[ii->base.pathlist_idx]; - cmp = ii->base.pathlist->_cmp(p, ie->path); + p = git_vector_get(&ii->base.pathlist, ii->pathlist_idx); + + /* trim trailing slashes that indicate an exact directory match */ + p_len = strlen(p); + + while (p_len && p[p_len-1] == '/') + p_len--; + + cmp = ii->base.strncomp(ie->path, p, p_len); + + /* we've matched the prefix - if the pathlist entry is equal to + * this entry, or if the pathlist entry is a folder (eg `foo/`) + * and this entry was beneath that, then continue. otherwise, + * sort the index entry path against the pathlist entry. + */ + if (cmp == 0) { + if (ie->path[p_len] == 0) + ; + else if (ie->path[p_len] == '/') + ; + else if (ie->path[p_len] < '/') + cmp = -1; + else if (ie->path[p_len] > '/') + cmp = 1; + } if (cmp < 0) { - ii->base.pathlist_idx++; + ii->current++; + ie = index_iterator__index_entry(ii); continue; } if (cmp > 0) { - ii->current++; - ie = index_iterator__index_entry(ii); + ii->pathlist_idx++; continue; } } @@ -861,13 +968,12 @@ static int index_iterator__reset( { index_iterator *ii = (index_iterator *)self; const git_index_entry *ie; - size_t pathlist_idx = 0; if (iterator__reset_range(self, start, end) < 0) return -1; ii->current = 0; - ii->base.pathlist_idx = 0; + ii->pathlist_idx = 0; /* 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. @@ -961,6 +1067,7 @@ struct fs_iterator { size_t root_len; uint32_t dirload_flags; int depth; + iterator_pathlist__match_t pathlist_match; int (*enter_dir_cb)(fs_iterator *self); int (*leave_dir_cb)(fs_iterator *self); @@ -971,6 +1078,7 @@ struct fs_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; @@ -1052,72 +1160,22 @@ static void fs_iterator__seek_frame_start( ff->index = 0; } -typedef enum { - DIRLOAD_PATHLIST_NONE = 0, - DIRLOAD_PATHLIST_EXACT = 1, - DIRLOAD_PATHLIST_DIRECTORY = 2, -} dirload_pathlist_match_t; - -static dirload_pathlist_match_t dirload_pathlist_match( - git_vector *pathlist, - const char *path, - size_t path_len, - int (*prefixcomp)(const char *a, const char *b)) -{ - const char *matched; - size_t idx; - - if (git_vector_bsearch2( - &idx, pathlist, pathlist->_cmp, path) != GIT_ENOTFOUND) - return DIRLOAD_PATHLIST_EXACT; - - /* the explicit path that we've seen in the directory iterator was - * not found - however, we may have hit a subdirectory in the directory - * iterator. examine the pathlist to see if it contains children of the - * current path. if so, indicate that we've found a subdirectory that - * is worth examining. - */ - while ((matched = git_vector_get(pathlist, idx)) != NULL && - prefixcomp(matched, path) == 0) { - - if (matched[path_len] == '/') - return DIRLOAD_PATHLIST_DIRECTORY; - else if (matched[path_len] > '/') - break; - - idx++; - } - - return DIRLOAD_PATHLIST_NONE; -} - -static int dirload_with_stat( - git_vector *contents, - const char *dirpath, - size_t prefix_len, - unsigned int flags, - const char *start_stat, - const char *end_stat, - git_vector *pathlist) +static int dirload_with_stat(git_vector *contents, size_t *filtered, fs_iterator *fi) { git_path_diriter diriter = GIT_PATH_DIRITER_INIT; const char *path; - int (*strncomp)(const char *a, const char *b, size_t sz); - int (*prefixcomp)(const char *a, const char *b); - size_t start_len = start_stat ? strlen(start_stat) : 0; - size_t end_len = end_stat ? strlen(end_stat) : 0; + 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; - dirload_pathlist_match_t pathlist_match = DIRLOAD_PATHLIST_EXACT; + iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH; int error; - strncomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? - git__strncasecmp : git__strncmp; - prefixcomp = (flags & GIT_PATH_DIR_IGNORE_CASE) != 0 ? - git__prefixcmp_icase : git__prefixcmp; + *filtered = 0; /* Any error here is equivalent to the dir not existing, skip over it */ - if ((error = git_path_diriter_init(&diriter, dirpath, flags)) < 0) { + if ((error = git_path_diriter_init( + &diriter, fi->path.ptr, fi->dirload_flags)) < 0) { error = GIT_ENOTFOUND; goto done; } @@ -1126,29 +1184,35 @@ static int dirload_with_stat( if ((error = git_path_diriter_fullpath(&path, &path_len, &diriter)) < 0) goto done; - assert(path_len > prefix_len); + assert(path_len > fi->root_len); /* remove the prefix if requested */ - path += prefix_len; - path_len -= prefix_len; + path += fi->root_len; + path_len -= fi->root_len; /* skip if before start_stat or after end_stat */ cmp_len = min(start_len, path_len); - if (cmp_len && strncomp(path, start_stat, cmp_len) < 0) + 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 && strncomp(path, end_stat, cmp_len) > 0) + if (cmp_len && fi->base.strncomp(path, fi->base.end, cmp_len) > 0) continue; - /* skip if we have a pathlist and this isn't in it. note that we - * haven't stat'd yet to know if it's a file or a directory, so this - * match for files like `foo` when we're looking for `foo/bar` + /* 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. */ - if (pathlist && - !(pathlist_match = dirload_pathlist_match( - pathlist, path, path_len, prefixcomp))) + 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))) { + + *filtered++; continue; + } /* Make sure to append two bytes, one for the path's null * termination, one for a possible trailing '/' for folders. @@ -1170,7 +1234,7 @@ static int dirload_with_stat( continue; } - if (pathlist_match == DIRLOAD_PATHLIST_DIRECTORY) { + if (pathlist_match == ITERATOR_PATHLIST_MATCH_DIRECTORY) { /* were looking for a directory, but this is a file */ git__free(ps); continue; @@ -1192,6 +1256,11 @@ static int dirload_with_stat( 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); } @@ -1211,6 +1280,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi) { int error; fs_iterator_frame *ff; + size_t filtered = 0; if (fi->depth > FS_MAX_DEPTH) { giterr_set(GITERR_REPOSITORY, @@ -1221,9 +1291,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi) ff = fs_iterator__alloc_frame(fi); GITERR_CHECK_ALLOC(ff); - error = dirload_with_stat(&ff->entries, - fi->path.ptr, fi->root_len, fi->dirload_flags, - fi->base.start, fi->base.end, fi->base.pathlist); + error = dirload_with_stat(&ff->entries, &filtered, fi); if (error < 0) { git_error_state last_error = { 0 }; @@ -1418,6 +1486,7 @@ static int fs_iterator__update_entry(fs_iterator *fi) 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 */ @@ -1450,6 +1519,7 @@ static int fs_iterator__initialize( return -1; } fi->root_len = fi->path.size; + fi->pathlist_match = ITERATOR_PATHLIST_MATCH_CHILD; fi->dirload_flags = (iterator__ignore_case(fi) ? GIT_PATH_DIR_IGNORE_CASE : 0) | @@ -1721,6 +1791,7 @@ void git_iterator_free(git_iterator *iter) iter->cb->free(iter); + git_vector_free(&iter->pathlist); git__free(iter->start); git__free(iter->end); @@ -1790,7 +1861,7 @@ int git_iterator_current_parent_tree( if (!(tf = tf->down) || tf->current >= tf->n_entries || !(te = tf->entries[tf->current]->te) || - ti->strncomp(scan, te->filename, te->filename_len) != 0) + ti->base.strncomp(scan, te->filename, te->filename_len) != 0) return 0; scan += te->filename_len; @@ -1923,9 +1994,18 @@ int git_iterator_advance_over_with_status( 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; - wi->is_ignored = GIT_IGNORE_TRUE; /* mark empty dirs ignored */ } else break; /* real error, stop here */ } else { diff --git a/src/iterator.h b/src/iterator.h index 0ea2bc05365..d2d61fbffdf 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -38,15 +38,14 @@ typedef enum { GIT_ITERATOR_INCLUDE_CONFLICTS = (1u << 5), } git_iterator_flag_t; - typedef struct { const char *start; const char *end; - /* paths to include in the iterator (literal). any paths not listed - * will be excluded. note that this vector may be resorted! + /* paths to include in the iterator (literal). if set, any paths not + * listed here will be excluded from iteration. */ - git_vector *pathlist; + git_strarray pathlist; /* flags, from above */ unsigned int flags; @@ -70,8 +69,9 @@ struct git_iterator { git_repository *repo; char *start; char *end; - git_vector *pathlist; - size_t pathlist_idx; + git_vector pathlist; + 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); size_t stat_calls; unsigned int flags; @@ -277,7 +277,8 @@ 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_EMPTY = 2, + GIT_ITERATOR_STATUS_FILTERED = 3 } git_iterator_status_t; /* Advance over a directory and check if it contains no files or just diff --git a/src/merge.c b/src/merge.c index 1460a50403f..5ba263bb4ec 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2357,7 +2357,8 @@ static int merge_check_index(size_t *conflicts, git_repository *repo, git_index } iter_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - iter_opts.pathlist = &staged_paths; + iter_opts.pathlist.strings = (char **)staged_paths.contents; + iter_opts.pathlist.count = staged_paths.length; if ((error = git_iterator_for_index(&iter_repo, index_repo, &iter_opts)) < 0 || (error = git_iterator_for_index(&iter_new, index_new, &iter_opts)) < 0 || diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 503d674502a..336f959f64e 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -486,7 +486,7 @@ void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) /* ensure that a single NULL pathspec is filtered out (like when using * fnmatch filtering) */ - opts.pathspec.strings = &pathspec; + opts.pathspec.count = 1; cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); @@ -581,6 +581,30 @@ void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) git_diff_free(diff); + /* ensure that multiple trailing slashes are ignored */ + pathspec = "subdir//////"; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + for (use_iterator = 0; use_iterator <= 1; use_iterator++) { + memset(&exp, 0, sizeof(exp)); + + if (use_iterator) + cl_git_pass(diff_foreach_via_iterator( + diff, diff_file_cb, NULL, NULL, NULL, &exp)); + else + cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); + + cl_assert_equal_i(3, exp.files); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); + } + + git_diff_free(diff); + /* ensure that fnmatching is completely disabled */ pathspec = "subdir/*"; diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 84dfbe113fe..5420aad4093 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -26,7 +26,7 @@ static void expect_iterator_items( const git_index_entry *entry; int count, error; int no_trees = !(git_iterator_flags(i) & GIT_ITERATOR_INCLUDE_TREES); - bool v = true; + bool v = false; if (expected_flat < 0) { v = true; expected_flat = -expected_flat; } if (expected_total < 0) { v = true; expected_total = -expected_total; } @@ -1099,7 +1099,8 @@ void test_repo_iterator__indexfilelist(void) /* 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 = &filelist; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ @@ -1147,7 +1148,8 @@ void test_repo_iterator__indexfilelist_2(void) cl_git_pass(git_vector_insert(&filelist, "e")); cl_git_pass(git_vector_insert(&filelist, "k/a")); - i_opts.pathlist = &filelist; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; i_opts.start = "b"; i_opts.end = "k/D"; @@ -1188,7 +1190,8 @@ void test_repo_iterator__indexfilelist_icase(void) /* All indexfilelist iterator tests are "autoexpand with no tree entries" */ - i_opts.pathlist = &filelist; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; i_opts.start = "c"; i_opts.end = "k/D"; @@ -1248,7 +1251,8 @@ void test_repo_iterator__workdirfilelist(void) /* 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 = &filelist; + 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); @@ -1297,7 +1301,8 @@ void test_repo_iterator__workdirfilelist_icase(void) g_repo = cl_git_sandbox_init("icase"); i_opts.flags = GIT_ITERATOR_DONT_IGNORE_CASE; - i_opts.pathlist = &filelist; + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; i_opts.start = "c"; i_opts.end = "k/D"; From 1af84271dd221343bc199207f3eb923781124928 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 18:35:57 -0400 Subject: [PATCH 017/101] tree_iterator: use a pathlist --- src/iterator.c | 35 ++++++++++++- tests/repo/iterator.c | 114 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 1 deletion(-) diff --git a/src/iterator.c b/src/iterator.c index bad24d13ee6..9bf56b65046 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -560,7 +560,7 @@ static int tree_iterator__update_entry(tree_iterator *ti) return 0; } -static int tree_iterator__current( +static int tree_iterator__current_internal( const git_index_entry **entry, git_iterator *self) { int error; @@ -583,6 +583,39 @@ static int tree_iterator__current( return 0; } +int tree_iterator__advance( + const git_index_entry **out, git_iterator *self); + +static int tree_iterator__current( + const git_index_entry **out, git_iterator *self) +{ + 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(&entry, self)) < 0) + return error; + + entry = NULL; + } + } + } while (!entry); + + if (out) + *out = entry; + + return error; +} + static int tree_iterator__advance_into( const git_index_entry **entry, git_iterator *self) { diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 5420aad4093..cb9d4cd4bf3 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -1332,3 +1332,117 @@ void test_repo_iterator__workdirfilelist_icase(void) 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); +} From 7b73739fddce91731bb53320ae6e43d7d7276169 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 18:56:39 -0400 Subject: [PATCH 018/101] checkout: use pathlist-based iterators --- src/checkout.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/checkout.c b/src/checkout.c index 311040d598e..de48c9e0168 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -2652,6 +2652,7 @@ int git_checkout_tree( git_index *index; git_tree *tree = NULL; git_iterator *tree_i = NULL; + git_iterator_options iter_opts = GIT_ITERATOR_OPTIONS_INIT; if (!treeish && !repo) { giterr_set(GITERR_CHECKOUT, @@ -2687,7 +2688,12 @@ int git_checkout_tree( if ((error = git_repository_index(&index, repo)) < 0) return error; - if (!(error = git_iterator_for_tree(&tree_i, tree, NULL))) + if ((opts->checkout_strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH)) { + iter_opts.pathlist.count = opts->paths.count; + iter_opts.pathlist.strings = opts->paths.strings; + } + + if (!(error = git_iterator_for_tree(&tree_i, tree, &iter_opts))) error = git_checkout_iterator(tree_i, index, opts); git_iterator_free(tree_i); From 71ef639e5f48be8e8b459cc705be7f58016256f8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 18:57:06 -0400 Subject: [PATCH 019/101] status test: brackets are now literal --- tests/status/worktree_init.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/status/worktree_init.c b/tests/status/worktree_init.c index cc7e126f110..9d5cfa5a320 100644 --- a/tests/status/worktree_init.c +++ b/tests/status/worktree_init.c @@ -191,10 +191,10 @@ void test_status_worktree_init__bracket_in_filename(void) cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET)); cl_assert(status_flags == GIT_STATUS_WT_NEW); - cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md")); - cl_assert(status_flags == GIT_STATUS_INDEX_NEW); + cl_git_fail_with(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"), GIT_ENOTFOUND); cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET)); + cl_assert(status_flags == GIT_STATUS_INDEX_NEW); git_index_free(index); git_repository_free(repo); From 56ed415a24d41e83169ac17f468a540260bd08ff Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 19:10:00 -0400 Subject: [PATCH 020/101] diff: drop `FILELIST_MATCH` Now that non-pathspec matching diffs are implemented at the iterator level, drop `FILELIST_MATCH`ing. --- include/git2/diff.h | 7 ------- src/merge.c | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index d3adf9f0164..491212182e6 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -165,13 +165,6 @@ typedef enum { /** Include unreadable files in the diff */ GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1u << 17), - /** Use literal path matching in the iterators. This is more broad - * than the DISABLE_PATHSPEC_MATCH flag. The caller must provide an - * array of paths (no patterns or prefixes). Only values included in - * that list will be returned. - */ - GIT_DIFF_ENABLE_FILELIST_MATCH = (1u << 18), - /* * Options controlling how output will be generated */ diff --git a/src/merge.c b/src/merge.c index 5ba263bb4ec..fc5088c82a5 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2405,7 +2405,7 @@ static int merge_check_workdir(size_t *conflicts, git_repository *repo, git_inde * will be applied by the merge (including conflicts). Ensure that there * are no changes in the workdir to these paths. */ - opts.flags |= GIT_DIFF_ENABLE_FILELIST_MATCH; + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; opts.pathspec.count = merged_paths->length; opts.pathspec.strings = (char **)merged_paths->contents; From d53c8880696856d695b0979c55136b06e45d6fbb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 19:25:47 -0400 Subject: [PATCH 021/101] iterator: saner pathlist matching for idx iterator Some nicer refactoring for index iteration walks. The index iterator doesn't binary search through the pathlist space, since it lacks directory entries, and would have to binary search each index entry and all its parents (eg, when presented with an index entry of `foo/bar/file.c`, you would have to look in the pathlist for `foo/bar/file.c`, `foo/bar` and `foo`). Since the index entries and the pathlist are both nicely sorted, we walk the index entries in lockstep with the pathlist like we do for other iteration/diff/merge walks. --- src/iterator.c | 138 +++++++++++++++++++++++------------------- src/iterator.h | 1 + tests/diff/workdir.c | 24 -------- tests/repo/iterator.c | 70 +++++++++++++++++++++ 4 files changed, 147 insertions(+), 86 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index 9bf56b65046..e35c8dc8500 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -75,7 +75,8 @@ static int iterator_pathlist__init(git_iterator *iter, git_strarray *pathspec) { size_t i; - if (git_vector_init(&iter->pathlist, pathspec->count, iter->strcomp) < 0) + if (git_vector_init(&iter->pathlist, pathspec->count, + (git_vector_cmp)iter->strcomp) < 0) return -1; for (i = 0; i < pathspec->count; i++) { @@ -98,7 +99,8 @@ static iterator_pathlist__match_t iterator_pathlist__match( size_t idx; int error; - error = git_vector_bsearch2(&idx, &iter->pathlist, iter->strcomp, path); + error = git_vector_bsearch2(&idx, &iter->pathlist, + (git_vector_cmp)iter->strcomp, path); if (error == 0) return ITERATOR_PATHLIST_MATCH; @@ -116,10 +118,7 @@ static iterator_pathlist__match_t iterator_pathlist__match( /* is this a literal directory entry (eg `foo/`) or a file beneath */ if (p[path_len] == '/') { - while (p[path_len] == '/') - path_len++; - - return (p[path_len] == '\0') ? + return (p[path_len+1] == '\0') ? ITERATOR_PATHLIST_MATCH_DIRECTORY : ITERATOR_PATHLIST_MATCH_CHILD; } @@ -133,10 +132,68 @@ 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; +} + +/* 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, iter->strcomp); + git_vector_set_cmp(&iter->pathlist, (git_vector_cmp)iter->strcomp); git_vector_sort(&iter->pathlist); + + iter->pathlist_walk_idx = 0; } @@ -583,13 +640,13 @@ static int tree_iterator__current_internal( return 0; } -int tree_iterator__advance( +static int tree_iterator__advance( const git_index_entry **out, git_iterator *self); static int tree_iterator__current( const git_index_entry **out, git_iterator *self) { - git_index_entry *entry = NULL; + const git_index_entry *entry = NULL; iterator_pathlist__match_t m; int error; @@ -797,9 +854,7 @@ static const git_index_entry *index_iterator__advance_over_unwanted( index_iterator *ii) { const git_index_entry *ie = index_iterator__index_entry(ii); - const char *p; - size_t p_len; - int cmp; + bool match; while (ie) { if (!iterator__include_conflicts(ii) && @@ -810,53 +865,17 @@ static const git_index_entry *index_iterator__advance_over_unwanted( } /* if we have a pathlist, this entry's path must be in it to be - * returned. otherwise, advance the pathlist entry or the iterator - * until we find the next path that we want to return. + * returned. walk the pathlist in unison with the index to + * compare paths. */ if (ii->base.pathlist.length) { + match = iterator_pathlist_walk__contains(&ii->base, ie->path); - if (ii->pathlist_idx >= ii->base.pathlist.length) { - ii->current = SIZE_MAX; - ie = NULL; - break; - } - - p = git_vector_get(&ii->base.pathlist, ii->pathlist_idx); - - /* trim trailing slashes that indicate an exact directory match */ - p_len = strlen(p); - - while (p_len && p[p_len-1] == '/') - p_len--; - - cmp = ii->base.strncomp(ie->path, p, p_len); - - /* we've matched the prefix - if the pathlist entry is equal to - * this entry, or if the pathlist entry is a folder (eg `foo/`) - * and this entry was beneath that, then continue. otherwise, - * sort the index entry path against the pathlist entry. - */ - if (cmp == 0) { - if (ie->path[p_len] == 0) - ; - else if (ie->path[p_len] == '/') - ; - else if (ie->path[p_len] < '/') - cmp = -1; - else if (ie->path[p_len] > '/') - cmp = 1; - } - - if (cmp < 0) { + if (!match) { ii->current++; ie = index_iterator__index_entry(ii); continue; } - - if (cmp > 0) { - ii->pathlist_idx++; - continue; - } } break; @@ -1006,7 +1025,8 @@ static int index_iterator__reset( return -1; ii->current = 0; - ii->pathlist_idx = 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. @@ -1193,7 +1213,7 @@ static void fs_iterator__seek_frame_start( ff->index = 0; } -static int dirload_with_stat(git_vector *contents, size_t *filtered, fs_iterator *fi) +static int dirload_with_stat(git_vector *contents, fs_iterator *fi) { git_path_diriter diriter = GIT_PATH_DIRITER_INIT; const char *path; @@ -1204,8 +1224,6 @@ static int dirload_with_stat(git_vector *contents, size_t *filtered, fs_iterator iterator_pathlist__match_t pathlist_match = ITERATOR_PATHLIST_MATCH; int error; - *filtered = 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) { @@ -1241,11 +1259,8 @@ static int dirload_with_stat(git_vector *contents, size_t *filtered, fs_iterator 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))) { - - *filtered++; + !(pathlist_match = iterator_pathlist__match(&fi->base, path, path_len))) continue; - } /* Make sure to append two bytes, one for the path's null * termination, one for a possible trailing '/' for folders. @@ -1313,7 +1328,6 @@ static int fs_iterator__expand_dir(fs_iterator *fi) { int error; fs_iterator_frame *ff; - size_t filtered = 0; if (fi->depth > FS_MAX_DEPTH) { giterr_set(GITERR_REPOSITORY, @@ -1324,7 +1338,7 @@ static int fs_iterator__expand_dir(fs_iterator *fi) ff = fs_iterator__alloc_frame(fi); GITERR_CHECK_ALLOC(ff); - error = dirload_with_stat(&ff->entries, &filtered, fi); + error = dirload_with_stat(&ff->entries, fi); if (error < 0) { git_error_state last_error = { 0 }; diff --git a/src/iterator.h b/src/iterator.h index d2d61fbffdf..59f87e9de0d 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -70,6 +70,7 @@ struct git_iterator { char *start; char *end; 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); diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index 336f959f64e..e8776917070 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -581,30 +581,6 @@ void test_diff_workdir__to_index_with_pathlist_disabling_fnmatch(void) git_diff_free(diff); - /* ensure that multiple trailing slashes are ignored */ - pathspec = "subdir//////"; - - cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); - - for (use_iterator = 0; use_iterator <= 1; use_iterator++) { - memset(&exp, 0, sizeof(exp)); - - if (use_iterator) - cl_git_pass(diff_foreach_via_iterator( - diff, diff_file_cb, NULL, NULL, NULL, &exp)); - else - cl_git_pass(git_diff_foreach(diff, diff_file_cb, NULL, NULL, NULL, &exp)); - - cl_assert_equal_i(3, exp.files); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_ADDED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_DELETED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); - cl_assert_equal_i(0, exp.file_status[GIT_DELTA_IGNORED]); - cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]); - } - - git_diff_free(diff); - /* ensure that fnmatching is completely disabled */ pathspec = "subdir/*"; diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index cb9d4cd4bf3..8eeb7d376cc 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -1162,6 +1162,76 @@ void test_repo_iterator__indexfilelist_2(void) 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; + + 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")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, 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; + + 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")); + + i_opts.pathlist.strings = (char **)filelist.contents; + i_opts.pathlist.count = filelist.length; + + i_opts.start = "b"; + i_opts.end = "k/D"; + + cl_git_pass(git_iterator_for_index(&i, index, &i_opts)); + expect_iterator_items(i, 8, NULL, 8, NULL); + git_iterator_free(i); + + git_index_free(index); + git_vector_free(&filelist); +} + void test_repo_iterator__indexfilelist_icase(void) { git_iterator *i; From 4d19bced3f8156b4255997b553fed99746c2a785 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 30 Aug 2015 19:33:18 -0400 Subject: [PATCH 022/101] iterator test: use new iter opts in fifo test --- tests/repo/iterator.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 8eeb7d376cc..619e11842f1 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -1047,6 +1047,7 @@ 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"); @@ -1056,9 +1057,11 @@ void test_repo_iterator__skips_fifos_and_such(void) 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", GIT_ITERATOR_INCLUDE_TREES | - GIT_ITERATOR_DONT_AUTOEXPAND, NULL, NULL)); + &i, "empty_standard_repo", &i_opts)); cl_git_pass(git_iterator_advance(&e, i)); /* .git */ cl_assert(S_ISDIR(e->mode)); From 03210cfa00d9fe4cd4cc236253b0d590c0d994cc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 31 Aug 2015 12:12:21 -0400 Subject: [PATCH 023/101] iterator test: handle case (in)sensitivity --- tests/repo/iterator.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 619e11842f1..9c4cc9ea4f3 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -1099,6 +1099,7 @@ void test_repo_iterator__indexfilelist(void) 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); @@ -1139,6 +1140,7 @@ void test_repo_iterator__indexfilelist_2(void) 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"); @@ -1149,16 +1151,23 @@ void test_repo_iterator__indexfilelist_2(void) 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, index, &i_opts)); - expect_iterator_items(i, 4, NULL, 4, NULL); + expect_iterator_items(i, expect, NULL, expect, NULL); git_iterator_free(i); git_index_free(index); @@ -1171,6 +1180,7 @@ void test_repo_iterator__indexfilelist_3(void) 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"); @@ -1186,14 +1196,20 @@ void test_repo_iterator__indexfilelist_3(void) 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, index, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); + expect_iterator_items(i, expect, NULL, expect, NULL); git_iterator_free(i); git_index_free(index); @@ -1206,6 +1222,7 @@ void test_repo_iterator__indexfilelist_4(void) 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"); @@ -1221,14 +1238,20 @@ void test_repo_iterator__indexfilelist_4(void) 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, index, &i_opts)); - expect_iterator_items(i, 8, NULL, 8, NULL); + expect_iterator_items(i, expect, NULL, expect, NULL); git_iterator_free(i); git_index_free(index); From 53c2296bfed972026809803415944b4dd4eff6d3 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 31 Aug 2015 19:41:43 -0400 Subject: [PATCH 024/101] iterator: better document GIT_DIFF_DISABLE_PATHSPEC_MATCH --- include/git2/diff.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/git2/diff.h b/include/git2/diff.h index 491212182e6..a0f6db35099 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -129,10 +129,12 @@ typedef enum { */ GIT_DIFF_INCLUDE_CASECHANGE = (1u << 11), - /** If the pathspec is set in the diff options, this flags means to - * use exact prefix matches instead of an fnmatch pattern. Each - * path in the list must either be a full filename or a subdirectory - * prefix. + /** If the pathspec is set in the diff options, this flags indicates + * that the paths will be treated as literal paths instead of + * fnmatch patterns. Each path in the list must either be a full + * path to a file or a directory. (A trailing slash indicates that + * the path will _only_ match a directory). If a directory is + * specified, all children will be included. */ GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1u << 12), From 1cef6b9f190587a78c65bd3168879be3eb37e0fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 3 Sep 2015 11:38:21 +0200 Subject: [PATCH 025/101] config: correct documentation for non-existent config file --- include/git2/config.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/git2/config.h b/include/git2/config.h index 05c3ad622bb..56b5431ac3b 100644 --- a/include/git2/config.h +++ b/include/git2/config.h @@ -171,6 +171,9 @@ GIT_EXTERN(int) git_config_new(git_config **out); * parsed; it's expected to be a native Git config file following * the default Git config syntax (see man git-config). * + * If the file does not exist, the file will still be added and it + * will be created the first time we write to it. + * * Note that the configuration object will free the file * automatically. * @@ -202,8 +205,7 @@ GIT_EXTERN(int) git_config_add_file_ondisk( * * @param out The configuration instance to create * @param path Path to the on-disk file to open - * @return 0 on success, GIT_ENOTFOUND when the file doesn't exist - * or an error code + * @return 0 on success, or an error code */ GIT_EXTERN(int) git_config_open_ondisk(git_config **out, const char *path); From 01fe83741ae162224637a27454be712a4195112d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 3 Sep 2015 13:35:15 +0200 Subject: [PATCH 026/101] Revert "Get rid of libssh2 embedding" The embedding was removed as a libssh2 release with Windows crypto support became available, but dependencies are still annoying so this ahs been requested again. This reverts commit 20dcb7315cd4c5760c68402998fd9e5a6bf5505d. --- CMakeLists.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c03c718c7e..293153f8673 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,10 @@ IF(MSVC) # are linking statically OPTION( STATIC_CRT "Link the static CRT libraries" ON ) + # If you want to embed a copy of libssh2 into libgit2, pass a + # path to libssh2 + OPTION( EMBED_SSH_PATH "Path to libssh2 to embed (Windows)" OFF ) + ADD_DEFINITIONS(-D_SCL_SECURE_NO_WARNINGS) ADD_DEFINITIONS(-D_CRT_SECURE_NO_DEPRECATE) ADD_DEFINITIONS(-D_CRT_NONSTDC_NO_DEPRECATE) @@ -172,6 +176,13 @@ IF (COREFOUNDATION_FOUND) ENDIF() +IF (WIN32 AND EMBED_SSH_PATH) + FILE(GLOB SRC_SSH "${EMBED_SSH_PATH}/src/*.c") + INCLUDE_DIRECTORIES("${EMBED_SSH_PATH}/include") + FILE(WRITE "${EMBED_SSH_PATH}/src/libssh2_config.h" "#define HAVE_WINCNG\n#define LIBSSH2_WINCNG\n#include \"../win32/libssh2_config.h\"") + ADD_DEFINITIONS(-DGIT_SSH) +ENDIF() + IF (WIN32 AND WINHTTP) ADD_DEFINITIONS(-DGIT_WINHTTP) INCLUDE_DIRECTORIES(deps/http-parser) @@ -501,7 +512,7 @@ ELSE() ENDIF() # Compile and link libgit2 -ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1} ${WIN_RC}) +ADD_LIBRARY(git2 ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1} ${WIN_RC}) TARGET_LINK_LIBRARIES(git2 ${SECURITY_DIRS}) TARGET_LINK_LIBRARIES(git2 ${COREFOUNDATION_DIRS}) TARGET_LINK_LIBRARIES(git2 ${SSL_LIBRARIES}) @@ -571,7 +582,7 @@ IF (BUILD_CLAR) ${CLAR_PATH}/clar.c PROPERTIES OBJECT_DEPENDS ${CLAR_PATH}/clar.suite) - ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SHA1}) + ADD_EXECUTABLE(libgit2_clar ${SRC_H} ${SRC_GIT2} ${SRC_OS} ${SRC_CLAR} ${SRC_TEST} ${SRC_ZLIB} ${SRC_HTTP} ${SRC_REGEX} ${SRC_SSH} ${SRC_SHA1}) TARGET_LINK_LIBRARIES(libgit2_clar ${COREFOUNDATION_DIRS}) TARGET_LINK_LIBRARIES(libgit2_clar ${SECURITY_DIRS}) From 81b76367571010aa83a3de38aecfee3c301e888d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 4 Sep 2015 13:30:49 +0200 Subject: [PATCH 027/101] index: put the icase insert choice in macros This should let us see more clearly what we're doing and avoid the ugly 'if' we need every time we want to interact with the map. --- src/index.c | 55 +++++++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/index.c b/src/index.c index e904ffcfebb..b9a78b21ffc 100644 --- a/src/index.c +++ b/src/index.c @@ -28,6 +28,29 @@ GIT__USE_IDXMAP GIT__USE_IDXMAP_ICASE +#define INSERT_IN_MAP_EX(idx, map, e, err) do { \ + if ((idx)->ignore_case) \ + git_idxmap_icase_insert((khash_t(idxicase) *) (map), (e), (e), (err)); \ + else \ + git_idxmap_insert((map), (e), (e), (err)); \ + } while (0) + +#define INSERT_IN_MAP(idx, e, err) INSERT_IN_MAP_EX(idx, (idx)->entries_map, e, err) + +#define LOOKUP_IN_MAP(p, idx, k) do { \ + if ((idx)->ignore_case) \ + (p) = git_idxmap_icase_lookup_index((khash_t(idxicase) *) index->entries_map, (k)); \ + else \ + (p) = git_idxmap_lookup_index(index->entries_map, (k)); \ + } while (0) + +#define DELETE_IN_MAP(idx, e) do { \ + if ((idx)->ignore_case) \ + git_idxmap_icase_delete((khash_t(idxicase) *) (idx)->entries_map, (e)); \ + else \ + git_idxmap_delete((idx)->entries_map, (e)); \ + } while (0) + static int index_apply_to_wd_diff(git_index *index, int action, const git_strarray *paths, unsigned int flags, git_index_matched_path_cb cb, void *payload); @@ -514,11 +537,7 @@ static int index_remove_entry(git_index *index, size_t pos) if (entry != NULL) git_tree_cache_invalidate_path(index->tree, entry->path); - if (index->ignore_case) - git_idxmap_icase_delete((khash_t(idxicase) *) index->entries_map, entry); - else - git_idxmap_delete(index->entries_map, entry); - + DELETE_IN_MAP(index, entry); error = git_vector_remove(&index->entries, pos); if (!error) { @@ -824,10 +843,7 @@ const git_index_entry *git_index_get_bypath( key.path = path; GIT_IDXENTRY_STAGE_SET(&key, stage); - if (index->ignore_case) - pos = git_idxmap_icase_lookup_index((khash_t(idxicase) *) index->entries_map, &key); - else - pos = git_idxmap_lookup_index(index->entries_map, &key); + LOOKUP_IN_MAP(pos, index, &key); if (git_idxmap_valid_index(index->entries_map, pos)) return git_idxmap_value_at(index->entries_map, pos); @@ -1161,10 +1177,7 @@ static int index_insert( error = git_vector_insert_sorted(&index->entries, entry, index_no_dups); if (error == 0) { - if (index->ignore_case) - git_idxmap_icase_insert((khash_t(idxicase) *) index->entries_map, entry, entry, error); else - git_idxmap_insert(index->entries_map, entry, entry, error); - + INSERT_IN_MAP(index, entry, error); } } @@ -1400,10 +1413,8 @@ int git_index_remove(git_index *index, const char *path, int stage) remove_key.path = path; GIT_IDXENTRY_STAGE_SET(&remove_key, stage); - if (index->ignore_case) - git_idxmap_icase_delete((khash_t(idxicase) *) index->entries_map, &remove_key); - else - git_idxmap_delete(index->entries_map, &remove_key); + + DELETE_IN_MAP(index, &remove_key); if (index_find(&position, index, path, 0, stage, false) < 0) { giterr_set( @@ -2236,10 +2247,7 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) goto done; } - if (index->ignore_case) - git_idxmap_icase_insert((khash_t(idxicase) *) index->entries_map, entry, entry, error); - else - git_idxmap_insert(index->entries_map, entry, entry, error); + INSERT_IN_MAP(index, entry, error); if (error < 0) { index_entry_free(entry); @@ -2690,10 +2698,7 @@ int git_index_read_tree(git_index *index, const git_tree *tree) kh_resize(idx, entries_map, entries.length); git_vector_foreach(&entries, i, e) { - if (index->ignore_case) - git_idxmap_icase_insert((git_idxmap_icase *) entries_map, e, e, error); - else - git_idxmap_insert(entries_map, e, e, error); + INSERT_IN_MAP_EX(index, entries_map, e, error); if (error < 0) { giterr_set(GITERR_INDEX, "failed to insert entry into map"); From c3733e56410d5bc73fcca0df8e062f84a0565f90 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Sep 2015 08:56:26 -0400 Subject: [PATCH 028/101] Add more headers to HTTP requests --- src/transports/http.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/transports/http.c b/src/transports/http.c index 87f3ee816a8..f9e5da2b1f4 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -54,6 +54,7 @@ typedef struct { char *redirect_url; const char *verb; char *chunk_buffer; + git_strarray *extra_headers; unsigned chunk_buffer_len; unsigned sent_request : 1, received_response : 1, @@ -193,6 +194,7 @@ static int gen_request( { http_subtransport *t = OWNING_SUBTRANSPORT(s); const char *path = t->connection_data.path ? t->connection_data.path : "/"; + size_t i; git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url); @@ -210,6 +212,13 @@ static int gen_request( } else git_buf_puts(buf, "Accept: */*\r\n"); + if (s->extra_headers) { + for (i = 0; i < s->extra_headers->count; i++) { + git_buf_puts(buf, s->extra_headers->strings[i]); + git_buf_puts(buf, "\r\n"); + } + } + /* Apply credentials to the request */ if (apply_credentials(buf, t) < 0) return -1; From 6af6e69009e207da851c2b8dff395babddb3bfa4 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Sep 2015 09:18:32 -0400 Subject: [PATCH 029/101] Put the extra headers on the connection_data instead --- src/netops.h | 1 + src/transports/http.c | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/netops.h b/src/netops.h index b7170a0f2e4..ea1072518db 100644 --- a/src/netops.h +++ b/src/netops.h @@ -70,6 +70,7 @@ typedef struct gitno_connection_data { char *user; char *pass; bool use_ssl; + git_strarray *extra_headers; } gitno_connection_data; /* diff --git a/src/transports/http.c b/src/transports/http.c index f9e5da2b1f4..d348310c16c 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -54,7 +54,6 @@ typedef struct { char *redirect_url; const char *verb; char *chunk_buffer; - git_strarray *extra_headers; unsigned chunk_buffer_len; unsigned sent_request : 1, received_response : 1, @@ -212,9 +211,9 @@ static int gen_request( } else git_buf_puts(buf, "Accept: */*\r\n"); - if (s->extra_headers) { - for (i = 0; i < s->extra_headers->count; i++) { - git_buf_puts(buf, s->extra_headers->strings[i]); + if (t->connection_data.extra_headers) { + for (i = 0; i < t->connection_data.extra_headers->count; i++) { + git_buf_puts(buf, t->connection_data.extra_headers->strings[i]); git_buf_puts(buf, "\r\n"); } } From ac9b51278996b864d0a2f7d61a827f89cbd4ff23 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Sep 2015 09:20:45 -0400 Subject: [PATCH 030/101] Pull extra_http_headers from the git_remote --- src/remote.c | 2 ++ src/remote.h | 1 + src/transports/http.c | 1 + 3 files changed, 4 insertions(+) diff --git a/src/remote.c b/src/remote.c index 7404bf49f53..b6fb87ece8a 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1643,6 +1643,8 @@ void git_remote_free(git_remote *remote) free_refspecs(&remote->passive_refspecs); git_vector_free(&remote->passive_refspecs); + git__free(remote->extra_http_headers); + git_push_free(remote->push); git__free(remote->url); git__free(remote->pushurl); diff --git a/src/remote.h b/src/remote.h index e696997f499..bfb20362bd7 100644 --- a/src/remote.h +++ b/src/remote.h @@ -32,6 +32,7 @@ struct git_remote { git_remote_autotag_option_t download_tags; int prune_refs; int passed_refspecs; + git_strarray *extra_http_headers; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/transports/http.c b/src/transports/http.c index d348310c16c..664cd80abf9 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -974,6 +974,7 @@ static int http_action( if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) && (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0) return ret; + t->connection_data.extra_headers = t->owner->owner->extra_http_headers; if ((ret = http_connect(t)) < 0) return ret; From 59d6128e2730b71da6fdebbdf9a4d04b909e9721 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 4 Sep 2015 09:36:50 -0400 Subject: [PATCH 031/101] Allow the world to set HTTP headers for remotes --- include/git2/remote.h | 3 +++ src/remote.c | 12 +++++++++++- src/remote.h | 2 +- src/transports/http.c | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 444fe5276de..40adca0ce6f 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -230,6 +230,9 @@ GIT_EXTERN(size_t) git_remote_refspec_count(const git_remote *remote); */ GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, size_t n); +GIT_EXTERN(int) git_remote_extra_http_headers(const git_remote *remote, git_strarray *extra_http_headers); +GIT_EXTERN(int) git_remote_set_extra_http_headers(git_remote *remote, const git_strarray extra_http_headers); + /** * Open a connection to a remote * diff --git a/src/remote.c b/src/remote.c index b6fb87ece8a..b7d82e70887 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1643,7 +1643,7 @@ void git_remote_free(git_remote *remote) free_refspecs(&remote->passive_refspecs); git_vector_free(&remote->passive_refspecs); - git__free(remote->extra_http_headers); + git_strarray_free(&remote->extra_http_headers); git_push_free(remote->push); git__free(remote->url); @@ -2154,6 +2154,16 @@ size_t git_remote_refspec_count(const git_remote *remote) return remote->refspecs.length; } +int git_remote_extra_http_headers(const git_remote *remote, git_strarray *extra_http_headers) +{ + return git_strarray_copy(extra_http_headers, &remote->extra_http_headers); +} + +int git_remote_set_extra_http_headers(git_remote *remote, const git_strarray extra_http_headers) +{ + return git_strarray_copy(&remote->extra_http_headers, &extra_http_headers); +} + const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) { return git_vector_get(&remote->refspecs, n); diff --git a/src/remote.h b/src/remote.h index bfb20362bd7..3a15288f3cb 100644 --- a/src/remote.h +++ b/src/remote.h @@ -32,7 +32,7 @@ struct git_remote { git_remote_autotag_option_t download_tags; int prune_refs; int passed_refspecs; - git_strarray *extra_http_headers; + git_strarray extra_http_headers; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/transports/http.c b/src/transports/http.c index 664cd80abf9..3cd5632f814 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -974,7 +974,7 @@ static int http_action( if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) && (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0) return ret; - t->connection_data.extra_headers = t->owner->owner->extra_http_headers; + t->connection_data.extra_headers = &t->owner->owner->extra_http_headers; if ((ret = http_connect(t)) < 0) return ret; From c097f7173daced0f86fd816a72ad5fc0f636484a Mon Sep 17 00:00:00 2001 From: Leo Yang Date: Mon, 17 Aug 2015 15:02:02 -0400 Subject: [PATCH 032/101] New API: git_index_find_prefix Find the first index entry matching a prefix. --- include/git2/index.h | 11 +++++++++++ src/index.c | 24 ++++++++++++++++++++++++ tests/index/tests.c | 21 +++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/include/git2/index.h b/include/git2/index.h index 7caf3ed78ea..176ba301ec1 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -643,6 +643,17 @@ GIT_EXTERN(int) git_index_update_all( */ GIT_EXTERN(int) git_index_find(size_t *at_pos, git_index *index, const char *path); +/** + * Find the first position of any entries matching a prefix. To find the first position + * of a path inside a given folder, suffix the prefix with a '/'. + * + * @param at_pos the address to which the position of the index entry is written (optional) + * @param index an existing index object + * @param prefix the prefix to search for + * @return 0 with valid value in at_pos; an error code otherwise + */ +GIT_EXTERN(int) git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix); + /**@}*/ /** @name Conflict Index Entry Functions diff --git a/src/index.c b/src/index.c index 53120e49c06..e32320f8036 100644 --- a/src/index.c +++ b/src/index.c @@ -1420,6 +1420,30 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) return error; } +int git_index_find_prefix(size_t *at_pos, git_index *index, const char *prefix) +{ + int error = 0; + size_t pos; + const git_index_entry *entry; + + if (git_mutex_lock(&index->lock) < 0) { + giterr_set(GITERR_OS, "Failed to lock index"); + return -1; + } + + index_find(&pos, index, prefix, strlen(prefix), GIT_INDEX_STAGE_ANY, false); + entry = git_vector_get(&index->entries, pos); + if (!entry || git__prefixcmp(entry->path, prefix) != 0) + error = GIT_ENOTFOUND; + + if (!error && at_pos) + *at_pos = pos; + + git_mutex_unlock(&index->lock); + + return error; +} + int git_index__find_pos( size_t *out, git_index *index, const char *path, size_t path_len, int stage) { diff --git a/tests/index/tests.c b/tests/index/tests.c index e1ff12ad0ff..60c21268154 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -155,6 +155,27 @@ void test_index_tests__find_in_empty(void) git_index_free(index); } +void test_index_tests__find_prefix(void) +{ + git_index *index; + const git_index_entry *entry; + size_t pos; + + cl_git_pass(git_index_open(&index, TEST_INDEX_PATH)); + + cl_git_pass(git_index_find_prefix(&pos, index, "src")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/block-sha1/sha1.c") == 0); + + cl_git_pass(git_index_find_prefix(&pos, index, "src/co")); + entry = git_index_get_byindex(index, pos); + cl_assert(git__strcmp(entry->path, "src/commit.c") == 0); + + cl_assert(GIT_ENOTFOUND == git_index_find_prefix(NULL, index, "blah")); + + git_index_free(index); +} + void test_index_tests__write(void) { git_index *index; From d83b2e9f51539287d5d2b1bd939ea9540fa6e5be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 5 Sep 2015 03:54:06 +0200 Subject: [PATCH 033/101] filebuf: follow symlinks when creating a lock file We create a lockfile to update files under GIT_DIR. Sometimes these files are actually located elsewhere and a symlink takes their place. In that case we should lock and update the file at its final location rather than overwrite the symlink. --- CHANGELOG.md | 3 ++ src/filebuf.c | 84 ++++++++++++++++++++++++++++++++++++++++++-- tests/core/filebuf.c | 53 ++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f28cbf3b4d..1f3c3ed6fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ v0.23 + 1 example `filter=*`. Consumers should examine the attributes parameter of the `check` function for details. +* Symlinks are now followed when locking a file, which can be + necessary when multiple worktrees share a base repository. + ### API additions * `git_config_lock()` has been added, which allow for diff --git a/src/filebuf.c b/src/filebuf.c index 838f4b4d265..2bbc210ba13 100644 --- a/src/filebuf.c +++ b/src/filebuf.c @@ -191,6 +191,81 @@ static int write_deflate(git_filebuf *file, void *source, size_t len) return 0; } +#define MAX_SYMLINK_DEPTH 5 + +static int resolve_symlink(git_buf *out, const char *path) +{ + int i, error, root; + ssize_t ret; + struct stat st; + git_buf curpath = GIT_BUF_INIT, target = GIT_BUF_INIT; + + if ((error = git_buf_grow(&target, GIT_PATH_MAX + 1)) < 0 || + (error = git_buf_puts(&curpath, path)) < 0) + return error; + + for (i = 0; i < MAX_SYMLINK_DEPTH; i++) { + error = p_lstat(curpath.ptr, &st); + if (error < 0 && errno == ENOENT) { + error = git_buf_puts(out, curpath.ptr); + goto cleanup; + } + + if (error < 0) { + giterr_set(GITERR_OS, "failed to stat '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (!S_ISLNK(st.st_mode)) { + error = git_buf_puts(out, curpath.ptr); + goto cleanup; + } + + ret = p_readlink(curpath.ptr, target.ptr, GIT_PATH_MAX); + if (ret < 0) { + giterr_set(GITERR_OS, "failed to read symlink '%s'", curpath.ptr); + error = -1; + goto cleanup; + } + + if (ret == GIT_PATH_MAX) { + giterr_set(GITERR_INVALID, "symlink target too long"); + error = -1; + goto cleanup; + } + + /* readlink(2) won't NUL-terminate for us */ + target.ptr[ret] = '\0'; + target.size = ret; + + root = git_path_root(target.ptr); + if (root >= 0) { + if ((error = git_buf_puts(&curpath, target.ptr)) < 0) + goto cleanup; + } else { + git_buf dir = GIT_BUF_INIT; + + if ((error = git_path_dirname_r(&dir, curpath.ptr)) < 0) + goto cleanup; + + git_buf_swap(&curpath, &dir); + git_buf_free(&dir); + + if ((error = git_path_apply_relative(&curpath, target.ptr)) < 0) + goto cleanup; + } + } + + giterr_set(GITERR_INVALID, "maximum symlink depth reached"); + error = -1; + +cleanup: + git_buf_free(&curpath); + git_buf_free(&target); + return error; +} + int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode) { int compression, error = -1; @@ -265,11 +340,14 @@ int git_filebuf_open(git_filebuf *file, const char *path, int flags, mode_t mode file->path_lock = git_buf_detach(&tmp_path); GITERR_CHECK_ALLOC(file->path_lock); } else { - path_len = strlen(path); + git_buf resolved_path = GIT_BUF_INIT; + + if ((error = resolve_symlink(&resolved_path, path)) < 0) + goto cleanup; /* Save the original path of the file */ - file->path_original = git__strdup(path); - GITERR_CHECK_ALLOC(file->path_original); + path_len = resolved_path.size; + file->path_original = git_buf_detach(&resolved_path); /* create the locking path by appending ".lock" to the original */ GITERR_CHECK_ALLOC_ADD(&alloc_len, path_len, GIT_FILELOCK_EXTLENGTH); diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c index 3f7dc856919..39d98ff7ef8 100644 --- a/tests/core/filebuf.c +++ b/tests/core/filebuf.c @@ -151,3 +151,56 @@ void test_core_filebuf__rename_error(void) cl_assert_equal_i(false, git_path_exists(test_lock)); } + +void test_core_filebuf__symlink_follow(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0777)); + cl_git_pass(p_symlink("target", source)); + + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + + /* The second time around, the target file does exist */ + cl_git_pass(git_filebuf_open(&file, source, 0, 0666)); + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_assert_equal_i(true, git_path_exists("linkdir/target.lock")); + + cl_git_pass(git_filebuf_commit(&file)); + cl_assert_equal_i(true, git_path_exists("linkdir/target")); + + git_filebuf_cleanup(&file); + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} + +void test_core_filebuf__symlink_depth(void) +{ + git_filebuf file = GIT_FILEBUF_INIT; + const char *dir = "linkdir", *source = "linkdir/link"; + +#ifdef GIT_WIN32 + cl_skip(); +#endif + + cl_git_pass(p_mkdir(dir, 0777)); + /* Endless loop */ + cl_git_pass(p_symlink("link", source)); + + cl_git_fail(git_filebuf_open(&file, source, 0, 0666)); + + cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); +} From 280adb3f942a1ce4f4939b7058209d0cd0467062 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 4 Aug 2015 16:51:00 -0500 Subject: [PATCH 034/101] index: canonicalize directory case when adding On case insensitive systems, when given a user-provided path in the higher-level index addition functions (eg `git_index_add_bypath` / `git_index_add_frombuffer`), examine the index to try to match the given path to an existing directory. Various mechanisms can cause the on-disk representation of a folder to not match the representation in HEAD or the index - for example, a case changing rename of some file `a/file.txt` to `A/file.txt` will update the paths in the index, but not rename the folder on disk. If a user subsequently adds `a/other.txt`, then this should be stored in the index as `A/other.txt`. --- src/index.c | 95 +++++++++++++++++++++++++-- tests/index/bypath.c | 148 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 7 deletions(-) diff --git a/src/index.c b/src/index.c index 6be73d2c7ee..be86f16cb14 100644 --- a/src/index.c +++ b/src/index.c @@ -1102,6 +1102,74 @@ static int check_file_directory_collision(git_index *index, return 0; } +static int canonicalize_directory_path( + git_index *index, git_index_entry *entry) +{ + const git_index_entry *match, *best = NULL; + char *search, *sep; + size_t pos, search_len, best_len; + + if (!index->ignore_case) + return 0; + + /* item already exists in the index, simply re-use the existing case */ + if ((match = git_index_get_bypath(index, entry->path, 0)) != NULL) { + memcpy((char *)entry->path, match->path, strlen(entry->path)); + return 0; + } + + /* nothing to do */ + if (strchr(entry->path, '/') == NULL) + return 0; + + if ((search = git__strdup(entry->path)) == NULL) + return -1; + + /* starting at the parent directory and descending to the root, find the + * common parent directory. + */ + while (!best && (sep = strrchr(search, '/'))) { + sep[1] = '\0'; + + search_len = strlen(search); + + git_vector_bsearch2( + &pos, &index->entries, index->entries_search_path, search); + + while ((match = git_vector_get(&index->entries, pos))) { + if (GIT_IDXENTRY_STAGE(match) != 0) { + /* conflicts do not contribute to canonical paths */ + } else if (memcmp(search, match->path, search_len) == 0) { + /* prefer an exact match to the input filename */ + best = match; + best_len = search_len; + break; + } else if (strncasecmp(search, match->path, search_len) == 0) { + /* continue walking, there may be a path with an exact + * (case sensitive) match later in the index, but use this + * as the best match until that happens. + */ + if (!best) { + best = match; + best_len = search_len; + } + } else { + break; + } + + pos++; + } + + sep[0] = '\0'; + } + + if (best) + memcpy((char *)entry->path, best->path, best_len); + + git__free(search); + return 0; +} + static int index_no_dups(void **old, void *new) { const git_index_entry *entry = new; @@ -1115,10 +1183,17 @@ static int index_no_dups(void **old, void *new) * it, then it will return an error **and also free the entry**. When * it replaces an existing entry, it will update the entry_ptr with the * actual entry in the index (and free the passed in one). + * trust_path is whether we use the given path, or whether (on case + * insensitive systems only) we try to canonicalize the given path to + * be within an existing directory. * trust_mode is whether we trust the mode in entry_ptr. */ static int index_insert( - git_index *index, git_index_entry **entry_ptr, int replace, bool trust_mode) + git_index *index, + git_index_entry **entry_ptr, + int replace, + bool trust_path, + bool trust_mode) { int error = 0; size_t path_length, position; @@ -1156,8 +1231,14 @@ static int index_insert( entry->mode = index_merge_mode(index, existing, entry->mode); } + /* canonicalize the directory name */ + if (!trust_path) + error = canonicalize_directory_path(index, entry); + /* look for tree / blob name collisions, removing conflicts if requested */ - error = check_file_directory_collision(index, entry, position, replace); + if (!error) + error = check_file_directory_collision(index, entry, position, replace); + if (error < 0) /* skip changes */; @@ -1258,7 +1339,7 @@ int git_index_add_frombuffer( git_oid_cpy(&entry->id, &id); entry->file_size = len; - if ((error = index_insert(index, &entry, 1, true)) < 0) + if ((error = index_insert(index, &entry, 1, true, true)) < 0) return error; /* Adding implies conflict was resolved, move conflict entries to REUC */ @@ -1317,7 +1398,7 @@ int git_index_add_bypath(git_index *index, const char *path) assert(index && path); if ((ret = index_entry_init(&entry, index, path)) == 0) - ret = index_insert(index, &entry, 1, false); + ret = index_insert(index, &entry, 1, false, false); /* If we were given a directory, let's see if it's a submodule */ if (ret < 0 && ret != GIT_EDIRECTORY) @@ -1343,7 +1424,7 @@ int git_index_add_bypath(git_index *index, const char *path) if ((ret = add_repo_as_submodule(&entry, index, path)) < 0) return ret; - if ((ret = index_insert(index, &entry, 1, false)) < 0) + if ((ret = index_insert(index, &entry, 1, false, false)) < 0) return ret; } else if (ret < 0) { return ret; @@ -1394,7 +1475,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) } if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 || - (ret = index_insert(index, &entry, 1, true)) < 0) + (ret = index_insert(index, &entry, 1, true, true)) < 0) return ret; git_tree_cache_invalidate_path(index->tree, entry->path); @@ -1555,7 +1636,7 @@ int git_index_conflict_add(git_index *index, /* Make sure stage is correct */ GIT_IDXENTRY_STAGE_SET(entries[i], i + 1); - if ((ret = index_insert(index, &entries[i], 0, true)) < 0) + if ((ret = index_insert(index, &entries[i], 0, true, true)) < 0) goto on_error; entries[i] = NULL; /* don't free if later entry fails */ diff --git a/tests/index/bypath.c b/tests/index/bypath.c index b607e173282..17bba6ad508 100644 --- a/tests/index/bypath.c +++ b/tests/index/bypath.c @@ -72,3 +72,151 @@ void test_index_bypath__add_hidden(void) cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode); #endif } + +void test_index_bypath__add_honors_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("just_a_dir/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("just_a_dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_2(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + + /* note that `git_index_add` does no checking to canonical directories */ + dummy.path = "Just_a_dir/file0.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/fileA.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_A_DIR/fileC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_dir/fileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "JUST_a_DIR/fileE.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_mkfile("submod2/just_a_dir/file2.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file3.txt", "This is another file"); + cl_git_mkfile("submod2/just_a_dir/file4.txt", "And another file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/File1.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JUST_A_DIR/file2.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/FILE3.txt")); + cl_git_pass(git_index_add_bypath(g_idx, "JusT_A_DIR/FILE4.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/File1.txt", 0)); + cl_assert_equal_s("just_a_dir/File1.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/file2.txt", 0)); + cl_assert_equal_s("JUST_A_DIR/file2.txt", entry->path); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/FILE3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file3.txt", "Rewritten"); + cl_git_pass(git_index_add_bypath(g_idx, "Just_A_Dir/file3.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "Just_A_Dir/file3.txt", 0)); + cl_assert_equal_s("Just_A_Dir/FILE3.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_3(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + + dummy.path = "just_a_dir/filea.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_A_Dir/fileB.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_A_DIR/FILEC.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "Just_a_DIR/FileD.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_git_mkfile("submod2/JuSt_A_DiR/fILEE.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/fILEE.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "JUST_A_DIR/fILEE.txt", 0)); + cl_assert_equal_s("just_a_dir/fILEE.txt", entry->path); +} + +void test_index_bypath__add_honors_existing_case_4(void) +{ + git_index_entry dummy = { { 0 } }; + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + dummy.mode = GIT_FILEMODE_BLOB; + + dummy.path = "just_a_dir/a/b/c/d/e/file1.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + dummy.path = "just_a_dir/a/B/C/D/E/file2.txt"; + cl_git_pass(git_index_add(g_idx, &dummy)); + + cl_must_pass(p_mkdir("submod2/just_a_dir/a", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y", 0777)); + cl_must_pass(p_mkdir("submod2/just_a_dir/a/b/z/y/x", 0777)); + + cl_git_mkfile("submod2/just_a_dir/a/b/z/y/x/FOO.txt", "This is a file"); + + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/A/b/Z/y/X/foo.txt", 0)); + cl_assert_equal_s("just_a_dir/a/b/Z/y/X/foo.txt", entry->path); +} + From a32bc85e84090299ab9ba56b2d8f1761b7d91873 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 7 Aug 2015 12:43:49 -0500 Subject: [PATCH 035/101] git_index_add: allow case changing renames On case insensitive platforms, allow `git_index_add` to provide a new path for an existing index entry. Previously, we would maintain the case in an index entry without the ability to change it (except by removing an entry and re-adding it.) Higher-level functions (like `git_index_add_bypath` and `git_index_add_frombuffers`) continue to keep the old path for easier usage. --- CHANGELOG.md | 5 +++++ src/index.c | 41 ++++++++++++++++++++++++++--------------- tests/index/bypath.c | 20 ++++++++++++++++++++ tests/index/rename.c | 31 +++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f3c3ed6fd3..6ade3e3b13e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,11 @@ v0.23 + 1 with which to implement the transactional/atomic semantics for the configuration backend. +* `git_index_add` will now use the case as provided by the caller on + case insensitive systems. Previous versions would keep the case as + it existed in the index. This does not affect the higher-level + `git_index_add_bypath` or `git_index_add_frombuffer` functions. + v0.23 ------ diff --git a/src/index.c b/src/index.c index be86f16cb14..3a7a3d80d48 100644 --- a/src/index.c +++ b/src/index.c @@ -977,16 +977,27 @@ static int index_entry_reuc_init(git_index_reuc_entry **reuc_out, return 0; } -static void index_entry_cpy(git_index_entry *tgt, const git_index_entry *src) +static void index_entry_cpy( + git_index_entry *tgt, + git_index *index, + const git_index_entry *src, + bool update_path) { const char *tgt_path = tgt->path; memcpy(tgt, src, sizeof(*tgt)); - tgt->path = tgt_path; /* reset to existing path data */ + + /* keep the existing path buffer, but update the path to the one + * given by the caller, if we trust it. + */ + tgt->path = tgt_path; + + if (index->ignore_case && update_path) + memcpy((char *)tgt->path, src->path, strlen(tgt->path)); } static int index_entry_dup( git_index_entry **out, - git_repository *repo, + git_index *index, const git_index_entry *src) { git_index_entry *entry; @@ -996,10 +1007,10 @@ static int index_entry_dup( return 0; } - if (index_entry_create(&entry, repo, src->path) < 0) + if (index_entry_create(&entry, INDEX_OWNER(index), src->path) < 0) return -1; - index_entry_cpy(entry, src); + index_entry_cpy(entry, index, src, false); *out = entry; return 0; } @@ -1247,7 +1258,7 @@ static int index_insert( */ else if (existing) { if (replace) - index_entry_cpy(existing, entry); + index_entry_cpy(existing, index, entry, trust_path); index_entry_free(entry); *entry_ptr = entry = existing; } @@ -1327,7 +1338,7 @@ int git_index_add_frombuffer( return -1; } - if (index_entry_dup(&entry, INDEX_OWNER(index), source_entry) < 0) + if (index_entry_dup(&entry, index, source_entry) < 0) return -1; error = git_blob_create_frombuffer(&id, INDEX_OWNER(index), buffer, len); @@ -1474,7 +1485,7 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) return -1; } - if ((ret = index_entry_dup(&entry, INDEX_OWNER(index), source_entry)) < 0 || + if ((ret = index_entry_dup(&entry, index, source_entry)) < 0 || (ret = index_insert(index, &entry, 1, true, true)) < 0) return ret; @@ -1600,9 +1611,9 @@ int git_index_conflict_add(git_index *index, assert (index); - if ((ret = index_entry_dup(&entries[0], INDEX_OWNER(index), ancestor_entry)) < 0 || - (ret = index_entry_dup(&entries[1], INDEX_OWNER(index), our_entry)) < 0 || - (ret = index_entry_dup(&entries[2], INDEX_OWNER(index), their_entry)) < 0) + if ((ret = index_entry_dup(&entries[0], index, ancestor_entry)) < 0 || + (ret = index_entry_dup(&entries[1], index, our_entry)) < 0 || + (ret = index_entry_dup(&entries[2], index, their_entry)) < 0) goto on_error; /* Validate entries */ @@ -2210,7 +2221,7 @@ static size_t read_entry( entry.path = (char *)path_ptr; - if (index_entry_dup(out, INDEX_OWNER(index), &entry) < 0) + if (index_entry_dup(out, index, &entry) < 0) return 0; return entry_size; @@ -2727,7 +2738,7 @@ static int read_tree_cb( entry->mode == old_entry->mode && git_oid_equal(&entry->id, &old_entry->id)) { - index_entry_cpy(entry, old_entry); + index_entry_cpy(entry, data->index, old_entry, false); entry->flags_extended = 0; } @@ -2859,7 +2870,7 @@ int git_index_read_index( if (diff < 0) { git_vector_insert(&remove_entries, (git_index_entry *)old_entry); } else if (diff > 0) { - if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0) + if ((error = index_entry_dup(&entry, index, new_entry)) < 0) goto done; git_vector_insert(&new_entries, entry); @@ -2870,7 +2881,7 @@ int git_index_read_index( if (git_oid_equal(&old_entry->id, &new_entry->id)) { git_vector_insert(&new_entries, (git_index_entry *)old_entry); } else { - if ((error = index_entry_dup(&entry, git_index_owner(index), new_entry)) < 0) + if ((error = index_entry_dup(&entry, index, new_entry)) < 0) goto done; git_vector_insert(&new_entries, entry); diff --git a/tests/index/bypath.c b/tests/index/bypath.c index 17bba6ad508..b152b091797 100644 --- a/tests/index/bypath.c +++ b/tests/index/bypath.c @@ -73,6 +73,26 @@ void test_index_bypath__add_hidden(void) #endif } +void test_index_bypath__add_keeps_existing_case(void) +{ + const git_index_entry *entry; + + if (!cl_repo_get_bool(g_repo, "core.ignorecase")) + clar__skip(); + + cl_git_mkfile("submod2/just_a_dir/file1.txt", "This is a file"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/file1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/file1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); + + cl_git_rewritefile("submod2/just_a_dir/file1.txt", "Updated!"); + cl_git_pass(git_index_add_bypath(g_idx, "just_a_dir/FILE1.txt")); + + cl_assert(entry = git_index_get_bypath(g_idx, "just_a_dir/FILE1.txt", 0)); + cl_assert_equal_s("just_a_dir/file1.txt", entry->path); +} + void test_index_bypath__add_honors_existing_case(void) { const git_index_entry *entry; diff --git a/tests/index/rename.c b/tests/index/rename.c index dd3cfa73293..ebaa9b7404a 100644 --- a/tests/index/rename.c +++ b/tests/index/rename.c @@ -48,3 +48,34 @@ void test_index_rename__single_file(void) cl_fixture_cleanup("rename"); } + +void test_index_rename__casechanging(void) +{ + git_repository *repo; + git_index *index; + const git_index_entry *entry; + git_index_entry new = {{0}}; + + p_mkdir("rename", 0700); + + cl_git_pass(git_repository_init(&repo, "./rename", 0)); + cl_git_pass(git_repository_index(&index, repo)); + + cl_git_mkfile("./rename/lame.name.txt", "new_file\n"); + + cl_git_pass(git_index_add_bypath(index, "lame.name.txt")); + cl_assert_equal_i(1, git_index_entrycount(index)); + cl_assert((entry = git_index_get_bypath(index, "lame.name.txt", 0))); + + memcpy(&new, entry, sizeof(git_index_entry)); + new.path = "LAME.name.TXT"; + + cl_git_pass(git_index_add(index, &new)); + cl_assert((entry = git_index_get_bypath(index, "LAME.name.TXT", 0))); + + if (cl_repo_get_bool(repo, "core.ignorecase")) + cl_assert_equal_i(1, git_index_entrycount(index)); + else + cl_assert_equal_i(2, git_index_entrycount(index)); +} + From 24f5b4e155f701c86ad6daecb24b4af83aa183d6 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 13:34:42 -0400 Subject: [PATCH 036/101] Drop extra_http_headers from git_remote --- include/git2/remote.h | 3 --- src/netops.h | 1 - src/remote.c | 12 ------------ src/remote.h | 1 - src/transports/http.c | 1 - 5 files changed, 18 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 40adca0ce6f..444fe5276de 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -230,9 +230,6 @@ GIT_EXTERN(size_t) git_remote_refspec_count(const git_remote *remote); */ GIT_EXTERN(const git_refspec *)git_remote_get_refspec(const git_remote *remote, size_t n); -GIT_EXTERN(int) git_remote_extra_http_headers(const git_remote *remote, git_strarray *extra_http_headers); -GIT_EXTERN(int) git_remote_set_extra_http_headers(git_remote *remote, const git_strarray extra_http_headers); - /** * Open a connection to a remote * diff --git a/src/netops.h b/src/netops.h index ea1072518db..b7170a0f2e4 100644 --- a/src/netops.h +++ b/src/netops.h @@ -70,7 +70,6 @@ typedef struct gitno_connection_data { char *user; char *pass; bool use_ssl; - git_strarray *extra_headers; } gitno_connection_data; /* diff --git a/src/remote.c b/src/remote.c index b7d82e70887..7404bf49f53 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1643,8 +1643,6 @@ void git_remote_free(git_remote *remote) free_refspecs(&remote->passive_refspecs); git_vector_free(&remote->passive_refspecs); - git_strarray_free(&remote->extra_http_headers); - git_push_free(remote->push); git__free(remote->url); git__free(remote->pushurl); @@ -2154,16 +2152,6 @@ size_t git_remote_refspec_count(const git_remote *remote) return remote->refspecs.length; } -int git_remote_extra_http_headers(const git_remote *remote, git_strarray *extra_http_headers) -{ - return git_strarray_copy(extra_http_headers, &remote->extra_http_headers); -} - -int git_remote_set_extra_http_headers(git_remote *remote, const git_strarray extra_http_headers) -{ - return git_strarray_copy(&remote->extra_http_headers, &extra_http_headers); -} - const git_refspec *git_remote_get_refspec(const git_remote *remote, size_t n) { return git_vector_get(&remote->refspecs, n); diff --git a/src/remote.h b/src/remote.h index 3a15288f3cb..e696997f499 100644 --- a/src/remote.h +++ b/src/remote.h @@ -32,7 +32,6 @@ struct git_remote { git_remote_autotag_option_t download_tags; int prune_refs; int passed_refspecs; - git_strarray extra_http_headers; }; const char* git_remote__urlfordirection(struct git_remote *remote, int direction); diff --git a/src/transports/http.c b/src/transports/http.c index 3cd5632f814..d348310c16c 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -974,7 +974,6 @@ static int http_action( if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) && (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0) return ret; - t->connection_data.extra_headers = &t->owner->owner->extra_http_headers; if ((ret = http_connect(t)) < 0) return ret; From 9da32a625564064908b61530318a1366ffff8217 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 10:18:54 -0400 Subject: [PATCH 037/101] Add custom_headers to git_push_options --- include/git2/remote.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/git2/remote.h b/include/git2/remote.h index 444fe5276de..a39e5a415a4 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -585,6 +585,11 @@ typedef struct { * Callbacks to use for this push operation */ git_remote_callbacks callbacks; + + /** + * Extra headers for this push operation + */ + git_strarray custom_headers; } git_push_options; #define GIT_PUSH_OPTIONS_VERSION 1 From 4f2b6093a64ead32f51a886186496821e003cee5 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 13:53:41 -0400 Subject: [PATCH 038/101] Tell the git_transport about the custom_headers --- include/git2/remote.h | 3 ++- include/git2/sys/transport.h | 5 +++++ src/push.c | 3 ++- src/push.h | 1 + src/remote.c | 21 ++++++++++++++++----- tests/network/remote/defaultbranch.c | 6 +++--- tests/network/remote/local.c | 8 ++++---- tests/network/remote/remotes.c | 6 +++--- tests/online/fetch.c | 12 ++++++------ tests/online/push.c | 2 +- 10 files changed, 43 insertions(+), 24 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index a39e5a415a4..9237ca25553 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -241,9 +241,10 @@ 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 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); +GIT_EXTERN(int) git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_strarray *custom_headers); /** * Get the remote repository's reference advertisement list diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 4a75b08327e..ca8617f3fe6 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -40,6 +40,11 @@ struct git_transport { git_transport_certificate_check_cb certificate_check_cb, void *payload); + /* Set custom headers for HTTP requests */ + int (*set_custom_headers)( + git_transport *transport, + const git_strarray *custom_headers); + /* Connect the transport to the remote repository, using the given * direction. */ int (*connect)( diff --git a/src/push.c b/src/push.c index a0d8a055062..3c9fa2f1bf8 100644 --- a/src/push.c +++ b/src/push.c @@ -73,6 +73,7 @@ int git_push_set_options(git_push *push, const git_push_options *opts) GITERR_CHECK_VERSION(opts, GIT_PUSH_OPTIONS_VERSION, "git_push_options"); push->pb_parallelism = opts->pb_parallelism; + push->custom_headers = &opts->custom_headers; return 0; } @@ -638,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)) < 0) + (error = git_remote_connect(push->remote, GIT_DIRECTION_PUSH, callbacks, push->custom_headers)) < 0) return error; if ((error = filter_refs(push->remote)) < 0 || diff --git a/src/push.h b/src/push.h index a847ee0d080..e32ad2f4dad 100644 --- a/src/push.h +++ b/src/push.h @@ -38,6 +38,7 @@ struct git_push { /* options */ unsigned pb_parallelism; + const git_strarray *custom_headers; }; /** diff --git a/src/remote.c b/src/remote.c index 7404bf49f53..9e907d2810f 100644 --- a/src/remote.c +++ b/src/remote.c @@ -687,7 +687,15 @@ int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs) cbs->certificate_check, cbs->payload); } -int git_remote_connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks) +int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) +{ + if (!t->set_custom_headers || !custom_headers) + return 0; + + 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) { git_transport *t; const char *url; @@ -726,6 +734,9 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re if (!t && (error = git_transport_new(&t, remote, url)) < 0) return error; + if ((error = set_transport_custom_headers(t, custom_headers)) != 0) + goto on_error; + if ((error = set_transport_callbacks(t, callbacks)) < 0 || (error = t->connect(t, url, credentials, payload, direction, flags)) != 0) goto on_error; @@ -893,7 +904,7 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, NULL)) < 0) goto on_error; if (ls_to_vector(&refs, remote) < 0) @@ -966,7 +977,7 @@ int git_remote_fetch( } /* Connect and download everything */ - if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs)) != 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, NULL)) != 0) return error; error = git_remote_download(remote, refspecs, opts); @@ -2384,7 +2395,7 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi cbs = &opts->callbacks; if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, &opts->custom_headers)) < 0) goto cleanup; free_refspecs(&remote->active_refspecs); @@ -2441,7 +2452,7 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_ assert(remote && refspecs); - if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs)) < 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, &opts->custom_headers)) < 0) return error; if ((error = git_remote_upload(remote, refspecs, opts)) < 0) diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c index e83755ef678..5edd79fb806 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)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(remote_b, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(g_remote, GIT_DIRECTION_FETCH, 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 5d726c9587b..4d990ab714f 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, 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)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, 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)); + cl_git_pass(git_remote_connect(localremote, GIT_DIRECTION_PUSH, 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 2fa21d46071..46abc6d332a 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)); + cl_git_pass(git_remote_connect(r, GIT_DIRECTION_PUSH, &callbacks, 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)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_fail(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); git_remote_free(remote); } diff --git a/tests/online/fetch.c b/tests/online/fetch.c index 72e7c24e3c0..c12df069fe3 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); cl_git_pass(git_remote_download(remote, NULL, NULL)); git_remote_disconnect(remote); - git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL); + git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, 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)); + cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL)); git_remote_disconnect(remote); cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); diff --git a/tests/online/push.c b/tests/online/push.c index 0b0892c97ab..13d364d306b 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -373,7 +373,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)); + cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, 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 From 276f6aa08d4cb35ad647b24bfa254b99af89e076 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 14:00:37 -0400 Subject: [PATCH 039/101] Hook up the custom_headers to the http transport --- src/transports/http.c | 7 +++---- src/transports/smart.c | 12 ++++++++++++ src/transports/smart.h | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index d348310c16c..764c6a97e12 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -211,10 +211,9 @@ static int gen_request( } else git_buf_puts(buf, "Accept: */*\r\n"); - if (t->connection_data.extra_headers) { - for (i = 0; i < t->connection_data.extra_headers->count; i++) { - git_buf_puts(buf, t->connection_data.extra_headers->strings[i]); - git_buf_puts(buf, "\r\n"); + if (t->owner->custom_headers) { + for (i = 0; i < t->owner->custom_headers->count; i++) { + git_buf_printf(buf, "%s\r\n", t->owner->custom_headers->strings[i]); } } diff --git a/src/transports/smart.c b/src/transports/smart.c index 31a2dec7bc9..15f45e11c61 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,6 +66,17 @@ static int git_smart__set_callbacks( return 0; } +static int git_smart__set_custom_headers( + git_transport *transport, + const git_strarray *custom_headers) +{ + transport_smart *t = (transport_smart *)transport; + + t->custom_headers = custom_headers; + + return 0; +} + int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; @@ -399,6 +410,7 @@ int git_transport_smart(git_transport **out, git_remote *owner, void *param) t->parent.version = GIT_TRANSPORT_VERSION; t->parent.set_callbacks = git_smart__set_callbacks; + t->parent.set_custom_headers = git_smart__set_custom_headers; t->parent.connect = git_smart__connect; t->parent.close = git_smart__close; t->parent.free = git_smart__free; diff --git a/src/transports/smart.h b/src/transports/smart.h index 4c728c7ccf9..2c87e0200f3 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -139,6 +139,7 @@ typedef struct { git_transport_message_cb error_cb; git_transport_certificate_check_cb certificate_check_cb; void *message_cb_payload; + const git_strarray *custom_headers; git_smart_subtransport *wrapped; git_smart_subtransport_stream *current_stream; transport_smart_caps caps; From 80ee25434d076b87d1a34e2dee467600013ae4ee Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 13:38:22 -0400 Subject: [PATCH 040/101] Teach winhttp about the extra headers --- src/transports/winhttp.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 0c43c4b0b9a..d28762db158 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -409,6 +409,23 @@ static int winhttp_stream_connect(winhttp_stream *s) } } + if (t->owner->custom_headers) { + for (i = 0; i < t->owner->custom_headers->count; i++) { + git_buf_clear(&buf); + git_buf_puts(&buf, t->owner->custom_headers->strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); + goto on_error; + } + + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } + } + } + /* If requested, disable certificate validation */ if (t->connection_data.use_ssl) { int flags; From 5d7cd57f9950fca5c60176c5b5a3673358386a05 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 14:15:29 -0400 Subject: [PATCH 041/101] Update another call to git_remote_connect --- examples/network/ls-remote.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/network/ls-remote.c b/examples/network/ls-remote.c index 21026562f6f..c9da79f5f99 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); + error = git_remote_connect(remote, GIT_DIRECTION_FETCH, &callbacks, NULL); if (error < 0) goto cleanup; From c82c2ba60f63095a3b418424d858277f5800b914 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Tue, 8 Sep 2015 14:17:59 -0400 Subject: [PATCH 042/101] o i --- src/transports/winhttp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index d28762db158..8e2bdd44fe0 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -277,6 +277,7 @@ static int winhttp_stream_connect(winhttp_stream *s) unsigned long disable_redirects = WINHTTP_DISABLE_REDIRECTS; int default_timeout = TIMEOUT_INFINITE; int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT; + int i; /* Prepare URL */ git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); From ea3f2c296aa39b9609db3d75c96837518beb12a3 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 8 Sep 2015 14:35:53 -0400 Subject: [PATCH 043/101] filebuf: ensure we can lock a hidden file --- tests/core/filebuf.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/core/filebuf.c b/tests/core/filebuf.c index 39d98ff7ef8..915e3cc342e 100644 --- a/tests/core/filebuf.c +++ b/tests/core/filebuf.c @@ -204,3 +204,29 @@ void test_core_filebuf__symlink_depth(void) cl_git_pass(git_futils_rmdir_r(dir, NULL, GIT_RMDIR_REMOVE_FILES)); } + +void test_core_filebuf__hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_filebuf file = GIT_FILEBUF_INIT; + char *dir = "hidden", *test = "hidden/test"; + bool hidden; + + cl_git_pass(p_mkdir(dir, 0666)); + cl_git_mkfile(test, "dummy content"); + + cl_git_pass(git_win32__set_hidden(test, true)); + cl_git_pass(git_win32__hidden(&hidden, test)); + cl_assert(hidden); + + cl_git_pass(git_filebuf_open(&file, test, 0, 0666)); + + cl_git_pass(git_filebuf_printf(&file, "%s\n", "libgit2 rocks")); + + cl_git_pass(git_filebuf_commit(&file)); + + git_filebuf_cleanup(&file); +#endif +} From 8e736a73cac81f09197eca32bf578f74ab21e97e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 8 Sep 2015 15:48:44 -0400 Subject: [PATCH 044/101] futils: ensure we can write a hidden file --- tests/core/futils.c | 68 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/core/futils.c diff --git a/tests/core/futils.c b/tests/core/futils.c new file mode 100644 index 00000000000..e7f7154ed2b --- /dev/null +++ b/tests/core/futils.c @@ -0,0 +1,68 @@ +#include "clar_libgit2.h" +#include "fileops.h" + +// Fixture setup and teardown +void test_core_futils__initialize(void) +{ + cl_must_pass(p_mkdir("futils", 0777)); +} + +void test_core_futils__cleanup(void) +{ + cl_fixture_cleanup("futils"); +} + +void test_core_futils__writebuffer(void) +{ + git_buf out = GIT_BUF_INIT, + append = GIT_BUF_INIT; + + /* create a new file */ + git_buf_puts(&out, "hello!\n"); + git_buf_printf(&out, "this is a %s\n", "test"); + + cl_git_pass(git_futils_writebuffer(&out, "futils/test-file", O_RDWR|O_CREAT, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + /* append some more data */ + git_buf_puts(&append, "And some more!\n"); + git_buf_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR|O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + git_buf_free(&out); + git_buf_free(&append); +} + +void test_core_futils__write_hidden_file(void) +{ +#ifndef GIT_WIN32 + cl_skip(); +#else + git_buf out = GIT_BUF_INIT, append = GIT_BUF_INIT; + bool hidden; + + git_buf_puts(&out, "hidden file.\n"); + git_futils_writebuffer(&out, "futils/test-file", O_RDWR | O_CREAT, 0666); + + cl_git_pass(git_win32__set_hidden("futils/test-file", true)); + + /* append some more data */ + git_buf_puts(&append, "And some more!\n"); + git_buf_put(&out, append.ptr, append.size); + + cl_git_pass(git_futils_writebuffer(&append, "futils/test-file", O_RDWR | O_APPEND, 0666)); + + cl_assert_equal_file(out.ptr, out.size, "futils/test-file"); + + cl_git_pass(git_win32__hidden(&hidden, "futils/test-file")); + cl_assert(hidden); + + git_buf_free(&out); + git_buf_free(&append); +#endif +} + From c49126c87f1124d0928d68d9191535e5ef4ecd25 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 08:34:35 -0400 Subject: [PATCH 045/101] Accept custom headers for fetch too --- include/git2/remote.h | 5 +++++ src/remote.c | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 9237ca25553..c42d967104d 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -547,6 +547,11 @@ typedef struct { * The default is to auto-follow tags. */ git_remote_autotag_option_t download_tags; + + /** + * Extra headers for this fetch operation + */ + git_strarray custom_headers; } git_fetch_options; #define GIT_FETCH_OPTIONS_VERSION 1 diff --git a/src/remote.c b/src/remote.c index 9e907d2810f..a374a84b332 100644 --- a/src/remote.c +++ b/src/remote.c @@ -895,16 +895,18 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const size_t i; git_vector *to_active, specs = GIT_VECTOR_INIT, refs = GIT_VECTOR_INIT; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; assert(remote); if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, NULL)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) < 0) goto on_error; if (ls_to_vector(&refs, remote) < 0) @@ -968,16 +970,18 @@ int git_remote_fetch( bool prune = false; git_buf reflog_msg_buf = GIT_BUF_INIT; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; update_fetchhead = opts->update_fetchhead; tagopt = opts->download_tags; } /* Connect and download everything */ - if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, NULL)) != 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH, cbs, custom_headers)) != 0) return error; error = git_remote_download(remote, refspecs, opts); From 35969c6839c0659ed3ced67bfc5b963662a721f2 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 08:58:23 -0400 Subject: [PATCH 046/101] Ignore NULL headers --- src/transports/http.c | 3 ++- src/transports/winhttp.c | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/transports/http.c b/src/transports/http.c index 764c6a97e12..73ea0504300 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -213,7 +213,8 @@ static int gen_request( if (t->owner->custom_headers) { for (i = 0; i < t->owner->custom_headers->count; i++) { - git_buf_printf(buf, "%s\r\n", t->owner->custom_headers->strings[i]); + if (t->owner->custom_headers->strings[i]) + git_buf_printf(buf, "%s\r\n", t->owner->custom_headers->strings[i]); } } diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 8e2bdd44fe0..5b00d9091fa 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -412,17 +412,19 @@ static int winhttp_stream_connect(winhttp_stream *s) if (t->owner->custom_headers) { for (i = 0; i < t->owner->custom_headers->count; i++) { - git_buf_clear(&buf); - git_buf_puts(&buf, t->owner->custom_headers->strings[i]); - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { - giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); - goto on_error; - } + if (t->owner->custom_headers->strings[i]) { + git_buf_clear(&buf); + git_buf_puts(&buf, t->owner->custom_headers->strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); + goto on_error; + } - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - giterr_set(GITERR_OS, "Failed to add a header to the request"); - goto on_error; + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; + } } } } From 8c876fa91d0783bf4e1f0e624b2dfa86574de4e3 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 09:11:16 -0400 Subject: [PATCH 047/101] Validate custom http headers --- src/transports/smart.c | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/transports/smart.c b/src/transports/smart.c index 15f45e11c61..ee49729aa93 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,11 +66,64 @@ static int git_smart__set_callbacks( return 0; } +bool is_valid(const char *custom_header) +{ + const char *c; + int name_len; + + if (custom_header == NULL) + return true; + + // Disallow \r and \n + c = strchr(custom_header, '\r'); + if (c != NULL) + return false; + c = strchr(custom_header, '\n'); + if (c != NULL) + return false; + + // Require a header name followed by : + c = strchr(custom_header, ':'); + if (c == NULL) + return false; + name_len = c - custom_header; + if (name_len < 1) + return false; + + // Disallow headers that we set + return git__strncmp("User-Agent", custom_header, name_len) == 0 && + git__strncmp("Host", custom_header, name_len) == 0 && + git__strncmp("Accept", custom_header, name_len) == 0 && + git__strncmp("Content-Type", custom_header, name_len) == 0 && + git__strncmp("Transfer-Encoding", custom_header, name_len) == 0 && + git__strncmp("Content-Length", custom_header, name_len) == 0; +} + +const char *find_invalid_header(const git_strarray *custom_headers) +{ + size_t i; + + if (custom_headers == NULL || custom_headers->count == 0) + return NULL; + + for (i = 0; i < custom_headers->count; i++) + if (!is_valid(custom_headers->strings[i])) + return custom_headers->strings[i]; + + return NULL; +} + static int git_smart__set_custom_headers( git_transport *transport, const git_strarray *custom_headers) { transport_smart *t = (transport_smart *)transport; + const char *invalid_header = find_invalid_header(custom_headers); + + if (invalid_header != NULL) { + giterr_set(GITERR_INVALID, "Illegal HTTP header '%s'", invalid_header); + return -1; + } t->custom_headers = custom_headers; From 66d90e7098ee2da76ff3351a305a17a38fb9282b Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 09:14:20 -0400 Subject: [PATCH 048/101] More specific names --- src/transports/smart.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index ee49729aa93..b4f8578dbd9 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,7 +66,7 @@ static int git_smart__set_callbacks( return 0; } -bool is_valid(const char *custom_header) +bool is_valid_custom_header(const char *custom_header) { const char *c; int name_len; @@ -99,7 +99,7 @@ bool is_valid(const char *custom_header) git__strncmp("Content-Length", custom_header, name_len) == 0; } -const char *find_invalid_header(const git_strarray *custom_headers) +const char *find_invalid_custom_header(const git_strarray *custom_headers) { size_t i; @@ -107,7 +107,7 @@ const char *find_invalid_header(const git_strarray *custom_headers) return NULL; for (i = 0; i < custom_headers->count; i++) - if (!is_valid(custom_headers->strings[i])) + if (!is_valid_custom_header(custom_headers->strings[i])) return custom_headers->strings[i]; return NULL; @@ -118,7 +118,7 @@ static int git_smart__set_custom_headers( const git_strarray *custom_headers) { transport_smart *t = (transport_smart *)transport; - const char *invalid_header = find_invalid_header(custom_headers); + const char *invalid_header = find_invalid_custom_header(custom_headers); if (invalid_header != NULL) { giterr_set(GITERR_INVALID, "Illegal HTTP header '%s'", invalid_header); From 3245896bb7527cb42d48faf68f33858c887f2b3d Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 13:18:26 -0400 Subject: [PATCH 049/101] Add a test for custom header validation Also, *some* custom headers actually are valid. --- src/transports/smart.c | 18 ++++++++++++------ tests/online/clone.c | 27 +++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index b4f8578dbd9..8388d9dc5bc 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,6 +66,10 @@ static int git_smart__set_callbacks( return 0; } +#define forbid_custom_header(disallowed_name) \ + if (strncmp(disallowed_name, custom_header, name_len) == 0) \ + return false + bool is_valid_custom_header(const char *custom_header) { const char *c; @@ -91,12 +95,14 @@ bool is_valid_custom_header(const char *custom_header) return false; // Disallow headers that we set - return git__strncmp("User-Agent", custom_header, name_len) == 0 && - git__strncmp("Host", custom_header, name_len) == 0 && - git__strncmp("Accept", custom_header, name_len) == 0 && - git__strncmp("Content-Type", custom_header, name_len) == 0 && - git__strncmp("Transfer-Encoding", custom_header, name_len) == 0 && - git__strncmp("Content-Length", custom_header, name_len) == 0; + forbid_custom_header("User-Agent"); + forbid_custom_header("Host"); + forbid_custom_header("Accept"); + forbid_custom_header("Content-Type"); + forbid_custom_header("Transfer-Encoding"); + forbid_custom_header("Content-Length"); + + return true; } const char *find_invalid_custom_header(const git_strarray *custom_headers) diff --git a/tests/online/clone.c b/tests/online/clone.c index 225b3abe2e8..b84be405cb0 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -213,6 +213,33 @@ void test_online_clone__custom_remote_callbacks(void) cl_assert(callcount > 0); } +void test_online_clone__custom_headers(void) +{ + char *empty_header = ""; + char *unnamed_header = "this is a header about nothing"; + char *newlines = "X-Custom: almost OK\n"; + char *conflict = "Accept: defined-by-git"; + char *ok = "X-Custom: this should be ok"; + + g_options.fetch_opts.custom_headers.count = 1; + + g_options.fetch_opts.custom_headers.strings = &empty_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &unnamed_header; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &newlines; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + g_options.fetch_opts.custom_headers.strings = &conflict; + cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); + + /* Finally, we got it right! */ + g_options.fetch_opts.custom_headers.strings = &ok; + cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options)); +} + static int cred_failure_cb( git_cred **cred, const char *url, From d29c5412aa91b279b1b2db4fe57dd6fe71272b91 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 10 Sep 2015 14:16:39 -0400 Subject: [PATCH 050/101] Avoid segfault when opts == NULL --- src/remote.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/remote.c b/src/remote.c index a374a84b332..ce6e13b3d39 100644 --- a/src/remote.c +++ b/src/remote.c @@ -2392,14 +2392,17 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi git_push *push; git_refspec *spec; const git_remote_callbacks *cbs = NULL; + const git_strarray *custom_headers = NULL; assert(remote); - if (opts) + if (opts) { cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; + } if (!git_remote_connected(remote) && - (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, &opts->custom_headers)) < 0) + (error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) goto cleanup; free_refspecs(&remote->active_refspecs); @@ -2448,15 +2451,17 @@ 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; if (opts) { GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks"); cbs = &opts->callbacks; + custom_headers = &opts->custom_headers; } assert(remote && refspecs); - if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, &opts->custom_headers)) < 0) + if ((error = git_remote_connect(remote, GIT_DIRECTION_PUSH, cbs, custom_headers)) < 0) return error; if ((error = git_remote_upload(remote, refspecs, opts)) < 0) From f17525b0ff4aa6b0909c56dff80b8bb0b68d7be1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 10 Aug 2015 18:36:27 +0200 Subject: [PATCH 051/101] submodule: refactor to be more explicit in the search When searching for information about a submdoule, let's be more explicit in what we expect to find. We currently insert a submodule into the map and change certain parameters when the config callback gets called. Switch to asking for the configuration we're interested in, rather than taking it in an arbitrary order. --- src/submodule.c | 363 +++++++++++++++++++++++++++++------------------- 1 file changed, 219 insertions(+), 144 deletions(-) diff --git a/src/submodule.c b/src/submodule.c index 7f52c361685..1d73dc24e60 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -89,9 +89,11 @@ __KHASH_IMPL( static int submodule_alloc(git_submodule **out, git_repository *repo, const char *name); 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 submodule_load_from_config(const git_config_entry *, void *); +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 *); static void submodule_get_index_status(unsigned int *, git_submodule *); static void submodule_get_wd_status(unsigned int *, git_submodule *, git_repository *, git_submodule_ignore_t); @@ -144,6 +146,41 @@ static int find_by_path(const git_config_entry *entry, void *payload) return 0; } +/** + * Find out the name of a submodule from its path + */ +static int name_from_path(git_buf *out, git_config *cfg, const char *path) +{ + const char *key = "submodule\\..*\\.path"; + git_config_iterator *iter; + git_config_entry *entry; + int error; + + if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0) + return error; + + while ((error = git_config_next(&entry, iter)) == 0) { + const char *fdot, *ldot; + /* TODO: this should maybe be strcasecmp on a case-insensitive fs */ + if (strcmp(path, entry->value) != 0) + continue; + + fdot = strchr(entry->name, '.'); + ldot = strrchr(entry->name, '.'); + + git_buf_clear(out); + git_buf_put(out, fdot + 1, ldot - fdot - 1); + return 0; + } + + if (error == GIT_ITEROVER) { + giterr_set(GITERR_SUBMODULE, "could not find a submodule name for '%s'", path); + error = GIT_ENOTFOUND; + } + + return error; +} + int git_submodule_lookup( git_submodule **out, /* NULL if user only wants to test existence */ git_repository *repo, @@ -280,11 +317,12 @@ static int submodule_get_or_create(git_submodule **out, git_repository *repo, gi return 0; } -static int submodules_from_index(git_strmap *map, git_index *idx) +static int submodules_from_index(git_strmap *map, git_index *idx, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; if ((error = git_iterator_for_index(&i, idx, NULL)) < 0) return error; @@ -293,6 +331,11 @@ static int submodules_from_index(git_strmap *map, git_index *idx) khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -301,7 +344,7 @@ static int submodules_from_index(git_strmap *map, git_index *idx) else sm->flags |= GIT_SUBMODULE_STATUS__INDEX_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_index_owner(idx), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_index_owner(idx), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_index_entry(sm, entry); git_submodule_free(sm); } @@ -311,16 +354,18 @@ static int submodules_from_index(git_strmap *map, git_index *idx) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; } -static int submodules_from_head(git_strmap *map, git_tree *head) +static int submodules_from_head(git_strmap *map, git_tree *head, git_config *cfg) { int error; git_iterator *i; const git_index_entry *entry; + git_buf name = GIT_BUF_INIT; if ((error = git_iterator_for_tree(&i, head, NULL)) < 0) return error; @@ -329,6 +374,11 @@ static int submodules_from_head(git_strmap *map, git_tree *head) khiter_t pos = git_strmap_lookup_index(map, entry->path); git_submodule *sm; + git_buf_clear(&name); + if (!name_from_path(&name, cfg, entry->path)) { + git_strmap_lookup_index(map, name.ptr); + } + if (git_strmap_valid_index(map, pos)) { sm = git_strmap_value_at(map, pos); @@ -337,7 +387,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) else sm->flags |= GIT_SUBMODULE_STATUS__HEAD_NOT_SUBMODULE; } else if (S_ISGITLINK(entry->mode)) { - if (!submodule_get_or_create(&sm, git_tree_owner(head), map, entry->path)) { + if (!submodule_get_or_create(&sm, git_tree_owner(head), map, name.ptr ? name.ptr : entry->path)) { submodule_update_from_head_data( sm, entry->mode, &entry->id); git_submodule_free(sm); @@ -348,6 +398,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) if (error == GIT_ITEROVER) error = 0; + git_buf_free(&name); git_iterator_free(i); return error; @@ -355,8 +406,7 @@ static int submodules_from_head(git_strmap *map, git_tree *head) /* If have_sm is true, sm is populated, otherwise map an repo are. */ typedef struct { - int have_sm; - git_submodule *sm; + git_config *mods; git_strmap *map; git_repository *repo; } lfc_data; @@ -369,7 +419,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) const char *wd = NULL; git_buf path = GIT_BUF_INIT; git_submodule *sm; - git_config_backend *mods = NULL; + git_config *mods = NULL; uint32_t mask; assert(repo && map); @@ -400,24 +450,28 @@ static int all_submodules(git_repository *repo, git_strmap *map) GIT_SUBMODULE_STATUS__WD_FLAGS | GIT_SUBMODULE_STATUS__WD_OID_VALID; + /* add submodule information from .gitmodules */ + if (wd) { + lfc_data data = { 0 }; + data.map = map; + data.repo = repo; + + if ((mods = gitmodules_snapshot(repo)) == NULL) + goto cleanup; + + data.mods = mods; + if ((error = git_config_foreach( + mods, submodule_load_each, &data)) < 0) + goto cleanup; + } /* add back submodule information from index */ if (idx) { - if ((error = submodules_from_index(map, idx)) < 0) + if ((error = submodules_from_index(map, idx, mods)) < 0) goto cleanup; } /* add submodule information from HEAD */ if (head) { - if ((error = submodules_from_head(map, head)) < 0) - goto cleanup; - } - /* add submodule information from .gitmodules */ - if (wd) { - lfc_data data = { 0 }; - data.map = map; - data.repo = repo; - if ((mods = open_gitmodules(repo, false)) != NULL && - (error = git_config_file_foreach( - mods, submodule_load_from_config, &data)) < 0) + if ((error = submodules_from_head(map, head, mods)) < 0) goto cleanup; } /* shallow scan submodules in work tree as needed */ @@ -428,7 +482,7 @@ static int all_submodules(git_repository *repo, git_strmap *map) } cleanup: - git_config_file_free(mods); + git_config_free(mods); /* TODO: if we got an error, mark submodule config as invalid? */ git_index_free(idx); git_tree_free(head); @@ -1370,8 +1424,7 @@ static int submodule_update_head(git_submodule *submodule) int git_submodule_reload(git_submodule *sm, int force) { int error = 0; - git_config_backend *mods; - lfc_data data = { 0 }; + git_config *mods; GIT_UNUSED(force); @@ -1390,28 +1443,14 @@ int git_submodule_reload(git_submodule *sm, int force) return error; /* refresh config data */ - mods = open_gitmodules(sm->repo, GITMODULES_EXISTING); + mods = gitmodules_snapshot(sm->repo); if (mods != NULL) { - git_buf path = GIT_BUF_INIT; - - git_buf_sets(&path, "submodule\\."); - git_buf_text_puts_escape_regex(&path, sm->name); - git_buf_puts(&path, "\\..*"); - - if (git_buf_oom(&path)) { - error = -1; - } else { - data.have_sm = 1; - data.sm = sm; - error = git_config_file_foreach_match( - mods, path.ptr, submodule_load_from_config, &data); - } + error = submodule_read_config(sm, mods); + git_config_free(mods); - git_buf_free(&path); - git_config_file_free(mods); - - if (error < 0) + if (error < 0) { return error; + } } /* refresh wd data */ @@ -1627,134 +1666,141 @@ int git_submodule_parse_recurse(git_submodule_recurse_t *out, const char *value) return 0; } -static int submodule_load_from_config( - const git_config_entry *entry, void *payload) +static int get_value(const char **out, git_config *cfg, git_buf *buf, const char *name, const char *field) { - const char *namestart, *property; - const char *key = entry->name, *value = entry->value, *path; - char *alternate = NULL, *replaced = NULL; - git_buf name = GIT_BUF_INIT; - lfc_data *data = payload; - git_submodule *sm; - int error = 0; - - if (git__prefixcmp(key, "submodule.") != 0) - return 0; - - namestart = key + strlen("submodule."); - property = strrchr(namestart, '.'); - - if (!property || (property == namestart)) - return 0; - - property++; - path = !strcasecmp(property, "path") ? value : NULL; + int error; - if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) - goto done; + git_buf_clear(buf); - if (data->have_sm) { - sm = data->sm; - } else { - khiter_t pos; - git_strmap *map = data->map; - pos = git_strmap_lookup_index(map, path ? path : name.ptr); - if (git_strmap_valid_index(map, pos)) { - sm = git_strmap_value_at(map, pos); - } else { - if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) - goto done; + if ((error = git_buf_printf(buf, "submodule.%s.%s", name, field)) < 0 || + (error = git_config_get_string(out, cfg, buf->ptr)) < 0) + return error; - git_strmap_insert(map, sm->name, sm, error); - assert(error != 0); - if (error < 0) - goto done; - error = 0; - } - } + return error; +} - sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; +static int submodule_read_config(git_submodule *sm, git_config *cfg) +{ + git_buf key = GIT_BUF_INIT; + const char *value; + int error, in_config = 0; - /* Only from config might we get differing names & paths. If so, then - * update the submodule and insert under the alternative key. + /* + * TODO: Look up path in index and if it is present but not a GITLINK + * then this should be deleted (at least to match git's behavior) */ - /* TODO: if case insensitive filesystem, then the following strcmps + if ((error = get_value(&value, cfg, &key, sm->name, "path")) == 0) { + in_config = 1; + /* + * TODO: if case insensitive filesystem, then the following strcmp * should be strcasecmp */ - - if (strcmp(sm->name, name.ptr) != 0) { /* name changed */ - if (sm->path && !strcmp(sm->path, name.ptr)) { /* already set as path */ - replaced = sm->name; - sm->name = sm->path; - } else { - if (sm->name != sm->path) - replaced = sm->name; - alternate = sm->name = git_buf_detach(&name); - } - } - else if (path && strcmp(path, sm->path) != 0) { /* path changed */ - if (!strcmp(sm->name, value)) { /* already set as name */ - replaced = sm->path; - sm->path = sm->name; - } else { - if (sm->path != sm->name) - replaced = sm->path; - if ((alternate = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - sm->path = alternate; + if (strcmp(sm->name, value) != 0) { + sm->path = git__strdup(value); + GITERR_CHECK_ALLOC(sm->path); } + } else if (error != GIT_ENOTFOUND) { + return error; } - /* Deregister under name being replaced */ - if (replaced) { - git__free(replaced); + if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { + in_config = 1; + sm->url = git__strdup(value); + GITERR_CHECK_ALLOC(sm->url); + } else if (error != GIT_ENOTFOUND) { + return error; } - /* TODO: Look up path in index and if it is present but not a GITLINK - * then this should be deleted (at least to match git's behavior) - */ - - if (path) - goto done; - - /* copy other properties into submodule entry */ - if (strcasecmp(property, "url") == 0) { - git__free(sm->url); - sm->url = NULL; - - if (value != NULL && (sm->url = git__strdup(value)) == NULL) { - error = -1; - goto done; - } + if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { + in_config = 1; + sm->branch = git__strdup(value); + GITERR_CHECK_ALLOC(sm->branch); + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "branch") == 0) { - git__free(sm->branch); - sm->branch = NULL; - if (value != NULL && (sm->branch = git__strdup(value)) == NULL) { - error = -1; - goto done; - } - } - else if (strcasecmp(property, "update") == 0) { + if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { + in_config = 1; if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - goto done; + return error; sm->update_default = sm->update; + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "fetchRecurseSubmodules") == 0) { + + if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { + in_config = 1; if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - goto done; + return error; sm->fetch_recurse_default = sm->fetch_recurse; + } else if (error != GIT_ENOTFOUND) { + return error; } - else if (strcasecmp(property, "ignore") == 0) { + + if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { + in_config = 1; if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - goto done; + return error; sm->ignore_default = sm->ignore; + } else if (error != GIT_ENOTFOUND) { + return error; } - /* ignore other unknown submodule properties */ + + if (in_config) + sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; + + return 0; +} + +static int submodule_load_each(const git_config_entry *entry, void *payload) +{ + lfc_data *data = payload; + const char *namestart, *property; + git_strmap_iter pos; + git_strmap *map = data->map; + git_buf name = GIT_BUF_INIT; + git_submodule *sm; + int error; + + if (git__prefixcmp(entry->name, "submodule.") != 0) + return 0; + + namestart = entry->name + strlen("submodule."); + property = strrchr(namestart, '.'); + + if (!property || (property == namestart)) + return 0; + + property++; + + if ((error = git_buf_set(&name, namestart, property - namestart -1)) < 0) + return error; + + /* + * Now that we have the submodule's name, we can use that to + * figure out whether it's in the map. If it's not, we create + * a new submodule, load the config and insert it. If it's + * already inserted, we've already loaded it, so we skip. + */ + pos = git_strmap_lookup_index(map, name.ptr); + if (git_strmap_valid_index(map, pos)) + return 0; + + if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) + goto done; + + if ((error = submodule_read_config(sm, data->mods)) < 0) { + git_submodule_free(sm); + goto done; + } + + git_strmap_insert(map, sm->name, sm, error); + assert(error != 0); + if (error < 0) + goto done; + + error = 0; done: git_buf_free(&name); @@ -1778,6 +1824,35 @@ static int submodule_load_from_wd_lite(git_submodule *sm) return 0; } +/** + * Returns a snapshot of $WORK_TREE/.gitmodules. + * + * We ignore any errors and just pretend the file isn't there. + */ +static git_config *gitmodules_snapshot(git_repository *repo) +{ + const char *workdir = git_repository_workdir(repo); + git_config *mods = NULL, *snap = NULL; + git_buf path = GIT_BUF_INIT; + + if (workdir != NULL) { + if (git_buf_joinpath(&path, workdir, GIT_MODULES_FILE) != 0) + return NULL; + + if (git_config_open_ondisk(&mods, path.ptr) < 0) + mods = NULL; + } + + git_buf_free(&path); + + if (mods) { + git_config_snapshot(&snap, mods); + git_config_free(mods); + } + + return snap; +} + static git_config_backend *open_gitmodules( git_repository *repo, int okay_to_create) From a3b9731ff8846870a4c5124cd57d2d797913ad1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 10 Sep 2015 21:23:03 +0200 Subject: [PATCH 052/101] submodule: add a test for a renamed submdoule dir --- tests/submodule/lookup.c | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index ecea694e511..5f161487176 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -333,3 +333,58 @@ void test_submodule_lookup__prefix_name(void) git_submodule_free(sm); } + +void test_submodule_lookup__renamed(void) +{ + const char *newpath = "sm_actually_changed"; + git_index *idx; + sm_lookup_data data; + + cl_git_pass(git_repository_index__weakptr(&idx, g_repo)); + + /* We're replicating 'git mv sm_unchanged sm_actually_changed' in this test */ + + cl_git_pass(p_rename("submod2/sm_unchanged", "submod2/sm_actually_changed")); + + /* Change the path in .gitmodules and stage it*/ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.gitmodules")); + cl_git_pass(git_config_set_string(cfg, "submodule.sm_unchanged.path", newpath)); + git_config_free(cfg); + + cl_git_pass(git_index_add_bypath(idx, ".gitmodules")); + } + + /* Change the worktree info in the the submodule's config */ + { + git_config *cfg; + + cl_git_pass(git_config_open_ondisk(&cfg, "submod2/.git/modules/sm_unchanged/config")); + cl_git_pass(git_config_set_string(cfg, "core.worktree", "../../../sm_actually_changed")); + git_config_free(cfg); + } + + /* Rename the entry in the index */ + { + const git_index_entry *e; + git_index_entry entry = { 0 }; + + e = git_index_get_bypath(idx, "sm_unchanged", 0); + cl_assert(e); + cl_assert_equal_i(GIT_FILEMODE_COMMIT, e->mode); + + entry.path = newpath; + entry.mode = GIT_FILEMODE_COMMIT; + git_oid_cpy(&entry.id, &e->id); + + cl_git_pass(git_index_remove(idx, "sm_unchanged", 0)); + cl_git_pass(git_index_add(idx, &entry)); + cl_git_pass(git_index_write(idx)); + } + + memset(&data, 0, sizeof(data)); + cl_git_pass(git_submodule_foreach(g_repo, sm_lookup_cb, &data)); + cl_assert_equal_i(8, data.count); +} From 8e177b2bb8cacc3ca9e1edd301a8aa1132f300af Mon Sep 17 00:00:00 2001 From: Bryan Woods Date: Thu, 10 Sep 2015 14:44:52 -0700 Subject: [PATCH 053/101] Fixing dangling pointers in git_mempack_reset git_mempack_reset was leaving free'd pointers in the oidmap. --- src/odb_mempack.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/odb_mempack.c b/src/odb_mempack.c index 34355270f62..538dfc521e7 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -154,6 +154,9 @@ void git_mempack_reset(git_odb_backend *_backend) }); git_array_clear(db->commits); + + git_oidmap_free(db->objects); + db->objects = git_oidmap_alloc(); } static void impl__free(git_odb_backend *_backend) From a1859e21f36f47f05771ef7915dfe89840d4f2c6 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 11 Sep 2015 17:38:28 -0400 Subject: [PATCH 054/101] iterator: advance the tree iterator smartly While advancing the tree iterator, if we advance over things that we aren't interested in, then call `current`. Which may *itself* call advance. While advancing the tree iterator, if we advance over things that we aren't interested in, then call `current`. Which may *itself* call advance. While advancing the tree iterator, if we advance over things that we aren't interested in, then call `current`. Which may *itself* call advance. While advancing the tree iterator, if we advance over things that we aren't interested in, then call `current`. Which may *itself* call advance. While advancing the tree iterator, if we advance over things that we aren't interested in, then call `current`. Which may *itself* call advance. Error: stack overflow. --- src/iterator.c | 98 ++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index e35c8dc8500..9f3f7a9c7b0 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -640,8 +640,53 @@ static int tree_iterator__current_internal( return 0; } -static int tree_iterator__advance( - const git_index_entry **out, git_iterator *self); +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) @@ -659,7 +704,7 @@ static int tree_iterator__current( self, entry->path, strlen(entry->path)); if (m != ITERATOR_PATHLIST_MATCH) { - if ((error = tree_iterator__advance(&entry, self)) < 0) + if ((error = tree_iterator__advance_internal(self)) < 0) return error; entry = NULL; @@ -673,60 +718,29 @@ static int tree_iterator__current( return error; } -static int tree_iterator__advance_into( +static int tree_iterator__advance( const git_index_entry **entry, git_iterator *self) { - int error = 0; - tree_iterator *ti = (tree_iterator *)self; + int error = tree_iterator__advance_internal(self); iterator__clear_entry(entry); - if (tree_iterator__at_tree(ti)) - error = tree_iterator__push_frame(ti); - - if (!error && entry) - error = tree_iterator__current(entry, self); + if (error < 0) + return error; - return error; + return tree_iterator__current(entry, self); } -static int tree_iterator__advance( +static int tree_iterator__advance_into( const git_index_entry **entry, git_iterator *self) { - int error; - tree_iterator *ti = (tree_iterator *)self; - tree_iterator_frame *tf = ti->head; + int error = tree_iterator__advance_into_internal(self); iterator__clear_entry(entry); - if (tf->current >= tf->n_entries) - return GIT_ITEROVER; - - if (!iterator__has_been_accessed(ti)) - return tree_iterator__current(entry, self); - - if (iterator__do_autoexpand(ti) && iterator__include_trees(ti) && - tree_iterator__at_tree(ti)) - return tree_iterator__advance_into(entry, 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) + if (error < 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(entry, self); - return tree_iterator__current(entry, self); } From 707f65372b4091ada386f2a04a05e94899e32bfc Mon Sep 17 00:00:00 2001 From: Bryan Woods Date: Fri, 11 Sep 2015 16:35:14 -0700 Subject: [PATCH 055/101] Removing memory leak in mempack's free It calls git_mempack_reset which reallocates the object array. git_oidmap_free is now called on it explicitly. --- src/odb_mempack.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/odb_mempack.c b/src/odb_mempack.c index 538dfc521e7..25f30590c2c 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -161,8 +161,12 @@ void git_mempack_reset(git_odb_backend *_backend) static void impl__free(git_odb_backend *_backend) { - git_mempack_reset(_backend); - git__free(_backend); + struct memory_packer_db *db = (struct memory_packer_db *)_backend; + + git_mempack_reset(db); + git_oidmap_free(db->objects); + + git__free(db); } int git_mempack_new(git_odb_backend **out) From 220d6f8a104661ae9edc4f96cd8f520ef4c4dc1d Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 11 Sep 2015 20:06:14 -0400 Subject: [PATCH 056/101] mempack: expose clear function --- src/odb_mempack.c | 5 +---- src/oidmap.h | 2 ++ 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/odb_mempack.c b/src/odb_mempack.c index 25f30590c2c..594a2784c46 100644 --- a/src/odb_mempack.c +++ b/src/odb_mempack.c @@ -155,17 +155,14 @@ void git_mempack_reset(git_odb_backend *_backend) git_array_clear(db->commits); - git_oidmap_free(db->objects); - db->objects = git_oidmap_alloc(); + git_oidmap_clear(db->objects); } static void impl__free(git_odb_backend *_backend) { struct memory_packer_db *db = (struct memory_packer_db *)_backend; - git_mempack_reset(db); git_oidmap_free(db->objects); - git__free(db); } diff --git a/src/oidmap.h b/src/oidmap.h index d2c451e7fe8..2cf208f5392 100644 --- a/src/oidmap.h +++ b/src/oidmap.h @@ -49,4 +49,6 @@ GIT_INLINE(khint_t) git_oidmap_hash(const git_oid *oid) #define git_oidmap_size(h) kh_size(h) +#define git_oidmap_clear(h) kh_clear(oid, h) + #endif From 92f7d32b59642c82c17027bc85a39100869280a7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 12 Sep 2015 13:46:22 -0400 Subject: [PATCH 057/101] diff::workdir: ensure ignored files are not returned Ensure that a diff with the workdir is not erroneously returning directories. --- tests/diff/workdir.c | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index e8776917070..89f9483a675 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -2054,3 +2054,46 @@ void test_diff_workdir__only_writes_index_when_necessary(void) git_index_free(index); } +void test_diff_workdir__to_index_pathlist(void) +{ + git_index *index; + git_diff *diff; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_vector pathlist = GIT_VECTOR_INIT; + + git_vector_insert(&pathlist, "foobar/asdf"); + git_vector_insert(&pathlist, "subdir/asdf"); + git_vector_insert(&pathlist, "ignored/asdf"); + + g_repo = cl_git_sandbox_init("status"); + + cl_git_mkfile("status/.gitignore", ".gitignore\n" "ignored/\n"); + + cl_must_pass(p_mkdir("status/foobar", 0777)); + cl_git_mkfile("status/foobar/one", "one\n"); + + cl_must_pass(p_mkdir("status/ignored", 0777)); + cl_git_mkfile("status/ignored/one", "one\n"); + cl_git_mkfile("status/ignored/two", "two\n"); + cl_git_mkfile("status/ignored/three", "three\n"); + + cl_git_pass(git_repository_index(&index, g_repo)); + + opts.flags = GIT_DIFF_INCLUDE_IGNORED; + opts.pathspec.strings = pathlist.contents; + opts.pathspec.count = pathlist.length; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + opts.flags |= GIT_DIFF_DISABLE_PATHSPEC_MATCH; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, index, &opts)); + cl_assert_equal_i(0, git_diff_num_deltas(diff)); + git_diff_free(diff); + + git_index_free(index); + git_vector_free(&pathlist); +} + From 8ab4d0e1e16bf42e4617a40a13d67385fc8fa40c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sat, 12 Sep 2015 15:32:18 -0400 Subject: [PATCH 058/101] diff: check pathspec on non-files When we're not doing pathspec matching, we let the iterator handle file matching for us. However, we can only trust the iterator to return *files* that match the pattern, because the iterator must return directories that are not strictly in the pathlist, but that are the parents of files that match the pattern, so that diff can later recurse into them. Thus, diff must examine non-files explicitly before including them in the delta list. --- src/diff.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/diff.c b/src/diff.c index 32c1d4aa5ae..d97dcd9d221 100644 --- a/src/diff.c +++ b/src/diff.c @@ -75,18 +75,27 @@ static int diff_insert_delta( } static bool diff_pathspec_match( - const char **matched_pathspec, git_diff *diff, const char *path) + const char **matched_pathspec, + git_diff *diff, + const git_index_entry *entry) { - /* The iterator has filtered out paths for us, so the fact that we're - * seeing this patch means that it must match the given path list. + 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 (DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH)) { - *matched_pathspec = path; + if ((S_ISLNK(entry->mode) || S_ISREG(entry->mode)) && + disable_pathspec_match) { + *matched_pathspec = entry->path; return true; } return git_pathspec__match( - &diff->pathspec, path, false, + &diff->pathspec, entry->path, disable_pathspec_match, DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE), matched_pathspec, NULL); } @@ -127,7 +136,7 @@ static int diff_delta__from_one( DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_INCLUDE_UNREADABLE)) return 0; - if (!diff_pathspec_match(&matched_pathspec, diff, entry->path)) + if (!diff_pathspec_match(&matched_pathspec, diff, entry)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -768,7 +777,7 @@ static int maybe_modified( const char *matched_pathspec; int error = 0; - if (!diff_pathspec_match(&matched_pathspec, diff, oitem->path)) + if (!diff_pathspec_match(&matched_pathspec, diff, oitem)) return 0; memset(&noid, 0, sizeof(noid)); From 1af5aecb965817901fa9134222bad4e0579de7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 13 Sep 2015 05:38:29 +0200 Subject: [PATCH 059/101] push: put the git_oid inline in the test structure These are small pieces of data, so there is no advantage to allocating them separately. Include the two ids inline in the struct we use to check that the expected and actual ids match. --- tests/online/push.c | 2 +- tests/online/push_util.c | 11 +++-------- tests/online/push_util.h | 4 ++-- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/online/push.c b/tests/online/push.c index 0b0892c97ab..efb763c2482 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -299,7 +299,7 @@ static void verify_update_tips_callback(git_remote *remote, expected_ref expecte goto failed; } - if (git_oid_cmp(expected_refs[i].oid, tip->new_oid) != 0) { + if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) { git_buf_printf(&msg, "Updated tip ID does not match expected ID"); failed = 1; goto failed; diff --git a/tests/online/push_util.c b/tests/online/push_util.c index cd483c7c028..eafec2f05e5 100644 --- a/tests/online/push_util.c +++ b/tests/online/push_util.c @@ -9,8 +9,6 @@ const git_oid OID_ZERO = {{ 0 }}; void updated_tip_free(updated_tip *t) { git__free(t->name); - git__free(t->old_oid); - git__free(t->new_oid); git__free(t); } @@ -46,14 +44,11 @@ int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid * updated_tip *t; record_callbacks_data *record_data = (record_callbacks_data *)data; - cl_assert(t = git__malloc(sizeof(*t))); + cl_assert(t = git__calloc(1, sizeof(*t))); cl_assert(t->name = git__strdup(refname)); - cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid))); - git_oid_cpy(t->old_oid, a); - - cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid))); - git_oid_cpy(t->new_oid, b); + git_oid_cpy(&t->old_oid, a); + git_oid_cpy(&t->new_oid, b); git_vector_insert(&record_data->updated_tips, t); diff --git a/tests/online/push_util.h b/tests/online/push_util.h index 822341bd269..570873cfea8 100644 --- a/tests/online/push_util.h +++ b/tests/online/push_util.h @@ -16,8 +16,8 @@ extern const git_oid OID_ZERO; typedef struct { char *name; - git_oid *old_oid; - git_oid *new_oid; + git_oid old_oid; + git_oid new_oid; } updated_tip; typedef struct { From 657afd359e0e49627addb092c05ddd00117614de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 13 Sep 2015 06:18:49 +0200 Subject: [PATCH 060/101] ignore: add test and adjust style and comment for dir with wildmatch The previous commit left the comment referencing the earlier state of the code, change it to explain the current logic. While here, change the logic to avoid repeating the copy of the base pattern. --- src/ignore.c | 16 +++++++++------- tests/status/ignore.c | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/ignore.c b/src/ignore.c index 1f33687bcbb..aedc1401e1a 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -89,18 +89,20 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match } /* - * If we're dealing with a directory (which we know via the - * strchr() check) we want to use 'dirname/' as the - * pattern so p_fnmatch() honours FNM_PATHNAME + * When dealing with a directory, we add '/' so + * p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR + * alone isn't enough as that's also set for nagations, so we + * need to check that NEGATIVE is off. */ git_buf_clear(&buf); if (rule->containing_dir) { git_buf_puts(&buf, rule->containing_dir); } - if (rule->flags & GIT_ATTR_FNMATCH_LEADINGDIR && !(rule->flags & GIT_ATTR_FNMATCH_NEGATIVE)) - error = git_buf_printf(&buf, "%s/*", rule->pattern); - else - error = git_buf_puts(&buf, rule->pattern); + + error = git_buf_puts(&buf, rule->pattern); + + if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR) + error = git_buf_PUTS(&buf, "/*"); if (error < 0) goto out; diff --git a/tests/status/ignore.c b/tests/status/ignore.c index ba1d69a9929..bbf8f491193 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -1022,3 +1022,20 @@ void test_status_ignore__negate_exact_previous(void) cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, ".buildpath")); cl_assert_equal_i(1, ignored); } + +void test_status_ignore__negate_starstar(void) +{ + int ignored; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + + cl_git_mkfile("empty_standard_repo/.gitignore", + "code/projects/**/packages/*\n" + "!code/projects/**/packages/repositories.config"); + + cl_git_pass(git_futils_mkdir_r("code/projects/foo/bar/packages", "empty_standard_repo", 0777)); + cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); + + cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); + cl_assert_equal_i(0, ignored); +} From 548cb334344a592678778e0357f3f09000d6cf5d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sun, 13 Sep 2015 16:32:24 +0200 Subject: [PATCH 061/101] Don't free config in `git_transaction_commit`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The config is not owned by the transaction, so please don’t free it. --- src/transaction.c | 1 - tests/config/write.c | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/transaction.c b/src/transaction.c index e9639bf970e..92e134e5bb0 100644 --- a/src/transaction.c +++ b/src/transaction.c @@ -331,7 +331,6 @@ int git_transaction_commit(git_transaction *tx) if (tx->type == TRANSACTION_CONFIG) { error = git_config_unlock(tx->cfg, true); - git_config_free(tx->cfg); tx->cfg = NULL; return error; diff --git a/tests/config/write.c b/tests/config/write.c index 3d9b1a16aa4..9ad11ab273a 100644 --- a/tests/config/write.c +++ b/tests/config/write.c @@ -670,6 +670,16 @@ void test_config_write__locking(void) git_transaction_free(tx); /* Now that we've unlocked it, we should see both updates */ + cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); + cl_assert_equal_s("other value", entry->value); + git_config_entry_free(entry); + cl_git_pass(git_config_get_entry(&entry, cfg, "section2.name3")); + cl_assert_equal_s("more value", entry->value); + git_config_entry_free(entry); + + git_config_free(cfg); + + /* We should also see the changes after reopening the config */ cl_git_pass(git_config_open_ondisk(&cfg, filename)); cl_git_pass(git_config_get_entry(&entry, cfg, "section.name")); cl_assert_equal_s("other value", entry->value); From 2cde210d47f94962443cc090896cc1d5ef88452f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 13 Sep 2015 13:52:19 -0400 Subject: [PATCH 062/101] diriter: test we can iterate root Ensure that we can iterate the filesystem root and that paths come back well-formed, not with an additional '/'. (eg, when iterating `c:/`, expect that we do not get some path like `c://autoexec.bat`). --- tests/core/dirent.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/core/dirent.c b/tests/core/dirent.c index d95e44196dc..2bd60269d7a 100644 --- a/tests/core/dirent.c +++ b/tests/core/dirent.c @@ -275,3 +275,32 @@ void test_core_dirent__diriter_with_fullname(void) check_counts(&sub); } + +void test_core_dirent__diriter_at_directory_root(void) +{ + git_path_diriter diriter = GIT_PATH_DIRITER_INIT; + const char *sandbox_path, *path; + char *root_path; + size_t path_len; + int root_offset, error; + + sandbox_path = clar_sandbox_path(); + cl_assert((root_offset = git_path_root(sandbox_path)) >= 0); + + cl_assert(root_path = git__calloc(1, root_offset + 2)); + strncpy(root_path, sandbox_path, root_offset + 1); + + cl_git_pass(git_path_diriter_init(&diriter, root_path, 0)); + + while ((error = git_path_diriter_next(&diriter)) == 0) { + cl_git_pass(git_path_diriter_fullpath(&path, &path_len, &diriter)); + + cl_assert(path_len > (size_t)(root_offset + 1)); + cl_assert(path[root_offset+1] != '/'); + } + + cl_assert_equal_i(error, GIT_ITEROVER); + + git_path_diriter_free(&diriter); + git__free(root_path); +} From 5a466befaf03021b5bf8f1c7d34d35c8cd316213 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 13 Sep 2015 13:59:41 -0400 Subject: [PATCH 063/101] diriter: don't double '/' on Windows The canonical directory path of the root directory of a volume on windows already ends in a slash (eg, `c:/`). This is true only at the volume root. Do not add a slash to paths in this case. --- src/path.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/path.c b/src/path.c index 9ce5d297894..d14a7699edd 100644 --- a/src/path.c +++ b/src/path.c @@ -1166,7 +1166,11 @@ static int diriter_update_paths(git_path_diriter *diriter) diriter->path[path_len-1] = L'\0'; git_buf_truncate(&diriter->path_utf8, diriter->parent_utf8_len); - git_buf_putc(&diriter->path_utf8, '/'); + + if (diriter->parent_utf8_len > 0 && + diriter->path_utf8.ptr[diriter->parent_utf8_len-1] != '/') + git_buf_putc(&diriter->path_utf8, '/'); + git_buf_put_w(&diriter->path_utf8, diriter->current.cFileName, filename_len); if (git_buf_oom(&diriter->path_utf8)) From 26d7cf6e57c08b497fed20f4e8b88efea52cbe42 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 11 Sep 2015 18:27:04 -0400 Subject: [PATCH 064/101] iterator: loop fs_iterator advance (don't recurse) --- src/iterator.c | 85 +++++++++++++++++++++++++++++--------------------- 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/src/iterator.c b/src/iterator.c index 9f3f7a9c7b0..e3a2abf665c 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -1432,19 +1432,14 @@ static int fs_iterator__advance_into( return error; } -static int fs_iterator__advance_over( - const git_index_entry **entry, git_iterator *self) +static void fs_iterator__advance_over_internal(git_iterator *self) { - int error = 0; fs_iterator *fi = (fs_iterator *)self; fs_iterator_frame *ff; fs_iterator_path_with_stat *next; - if (entry != NULL) - *entry = NULL; - while (fi->entry.path != NULL) { - ff = fi->stack; + ff = fi->stack; next = git_vector_get(&ff->entries, ++ff->index); if (next != NULL) @@ -1452,8 +1447,19 @@ static int fs_iterator__advance_over( fs_iterator__pop_frame(fi, ff, false); } +} - error = fs_iterator__update_entry(fi); +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); @@ -1530,41 +1536,50 @@ static int fs_iterator__update_entry(fs_iterator *fi) { fs_iterator_path_with_stat *ps; - memset(&fi->entry, 0, sizeof(fi->entry)); + while (true) { + memset(&fi->entry, 0, sizeof(fi->entry)); - if (!fi->stack) - return GIT_ITEROVER; + if (!fi->stack) + return GIT_ITEROVER; - ps = git_vector_get(&fi->stack->entries, fi->stack->index); - if (!ps) - return GIT_ITEROVER; + 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; + 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; + 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); + 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); + /* 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) - return fs_iterator__advance_over(NULL, (git_iterator *)fi); + /* 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); + 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, (git_iterator *)fi); - if (error != GIT_ENOTFOUND) - return error; - giterr_clear(); - return fs_iterator__advance_over(NULL, (git_iterator *)fi); + /* 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); + + if (error != GIT_ENOTFOUND) + return error; + + giterr_clear(); + fs_iterator__advance_over_internal(&fi->base); + continue; + } + + break; } return 0; From 9d905541bf372cbb17e82eea6dc9c6ac36ab6ea7 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Sun, 13 Sep 2015 14:18:08 -0400 Subject: [PATCH 065/101] diriter: don't double '/' on posix The canonical directory path of the root directory of a volume on POSIX already ends in a slash (eg, `/`). This is true only at the root. Do not add a slash to paths in this case. --- src/path.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/path.c b/src/path.c index d14a7699edd..cb11acee3e1 100644 --- a/src/path.c +++ b/src/path.c @@ -1319,7 +1319,11 @@ int git_path_diriter_next(git_path_diriter *diriter) #endif git_buf_truncate(&diriter->path, diriter->parent_len); - git_buf_putc(&diriter->path, '/'); + + if (diriter->parent_len > 0 && + diriter->path.ptr[diriter->parent_len-1] != '/') + git_buf_putc(&diriter->path, '/'); + git_buf_put(&diriter->path, filename, filename_len); if (git_buf_oom(&diriter->path)) From 4cc355c97bd3a8c490759c489cfdcc9ab37e7f39 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 14 Sep 2015 13:58:38 -0400 Subject: [PATCH 066/101] clone::nonetwork: don't use fixed size buffer --- tests/clone/nonetwork.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c index 44a503818da..7ebf19f4633 100644 --- a/tests/clone/nonetwork.c +++ b/tests/clone/nonetwork.c @@ -297,16 +297,19 @@ static void assert_correct_reflog(const char *name) { git_reflog *log; const git_reflog_entry *entry; - char expected_log_message[128] = {0}; + git_buf expected_message = GIT_BUF_INIT; - sprintf(expected_log_message, "clone: from %s", cl_git_fixture_url("testrepo.git")); + git_buf_printf(&expected_message, + "clone: from %s", cl_git_fixture_url("testrepo.git")); cl_git_pass(git_reflog_read(&log, g_repo, name)); cl_assert_equal_i(1, git_reflog_entrycount(log)); entry = git_reflog_entry_byindex(log, 0); - cl_assert_equal_s(expected_log_message, git_reflog_entry_message(entry)); + cl_assert_equal_s(expected_message.ptr, git_reflog_entry_message(entry)); git_reflog_free(log); + + git_buf_free(&expected_message); } void test_clone_nonetwork__clone_updates_reflog_properly(void) From 8452fecc8fc14a59d37c7f06c9355e560aa66ed0 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 14 Sep 2015 14:05:01 -0400 Subject: [PATCH 067/101] cl_git_path_url: assert sane static buffer size --- tests/clar_libgit2.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index cc687baeb20..61442f88b8d 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -299,6 +299,8 @@ const char* cl_git_path_url(const char *path) in_buf++; } + cl_assert(url_buf.size < 4096); + strncpy(url, git_buf_cstr(&url_buf), 4096); git_buf_free(&url_buf); git_buf_free(&path_buf); From 6124d983b6970f99ec7c93e480896806a83e7198 Mon Sep 17 00:00:00 2001 From: Matti Virolainen Date: Mon, 1 Jun 2015 11:16:36 +0300 Subject: [PATCH 068/101] Check that an executable in index is not an executable after checkout. --- tests/checkout/tree.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index be4019822f4..577409511fd 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -973,6 +973,17 @@ void test_checkout_tree__filemode_preserved_in_index(void) git_commit_free(commit); + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); + cl_assert_equal_i(0100644, entry->mode); + + git_commit_free(commit); + + git_index_free(index); } From 33cad995899c960c777b3545dd58fd342ffdc64a Mon Sep 17 00:00:00 2001 From: Matti Virolainen Date: Mon, 1 Jun 2015 14:31:49 +0300 Subject: [PATCH 069/101] Check that checkout preserves filemode in working directory. --- tests/checkout/tree.c | 63 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 577409511fd..9217c12d917 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -987,6 +987,69 @@ void test_checkout_tree__filemode_preserved_in_index(void) git_index_free(index); } +mode_t read_filemode(const char *path) +{ + struct stat st; + char pathabs[256] = {0}; + + strcat(pathabs, clar_sandbox_path()); + strcat(pathabs, "/testrepo/"); + strcat(pathabs, path); + cl_must_pass(p_stat(pathabs, &st)); + + return st.st_mode; +} + +void test_checkout_tree__filemode_preserved_in_workdir(void) +{ +#ifndef GIT_WIN32 + git_oid executable_oid; + git_commit *commit; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + /* test a freshly added executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert_equal_i(0100755, read_filemode("executable.txt")); + + git_commit_free(commit); + + + /* Now start with a commit which has a text file */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert_equal_i(0100644, read_filemode("a/b.txt")); + + git_commit_free(commit); + + + /* And then check out to a commit which converts the text file to an executable */ + cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert_equal_i(0100755, read_filemode("a/b.txt")); + + git_commit_free(commit); + + + /* Finally, check out the text file again and check that the exec bit is cleared */ + cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9")); + cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); + + cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); + cl_assert_equal_i(0100644, read_filemode("a/b.txt")); + + git_commit_free(commit); +#endif +} + void test_checkout_tree__removes_conflicts(void) { git_oid commit_id; From b4d183a77aa01cac406aecdff8908f10cb3501cb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 16 Sep 2015 04:12:47 +0000 Subject: [PATCH 070/101] checkout::tree tests: don't use static buffer --- tests/checkout/tree.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 9217c12d917..3b84f43ce05 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -989,15 +989,18 @@ void test_checkout_tree__filemode_preserved_in_index(void) mode_t read_filemode(const char *path) { + git_buf fullpath = GIT_BUF_INIT; struct stat st; - char pathabs[256] = {0}; + mode_t result; - strcat(pathabs, clar_sandbox_path()); - strcat(pathabs, "/testrepo/"); - strcat(pathabs, path); - cl_must_pass(p_stat(pathabs, &st)); + git_buf_joinpath(&fullpath, "testrepo", path); + cl_must_pass(p_stat(fullpath.ptr, &st)); - return st.st_mode; + result = GIT_PERMS_IS_EXEC(st.st_mode) ? 0100755 : 0100644; + + git_buf_free(&fullpath); + + return result; } void test_checkout_tree__filemode_preserved_in_workdir(void) From 6fe322843b98f0f9101258c88c820f230eec5f64 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 16 Sep 2015 10:17:54 -0400 Subject: [PATCH 071/101] checkout::tree tests: don't use hardcoded mode --- tests/checkout/tree.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 3b84f43ce05..5692e1f2566 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -946,7 +946,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0)); - cl_assert_equal_i(0100755, entry->mode); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -957,7 +957,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert_equal_i(0100644, entry->mode); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -968,7 +968,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert_equal_i(0100755, entry->mode); + cl_assert(GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -979,7 +979,7 @@ void test_checkout_tree__filemode_preserved_in_index(void) cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0)); - cl_assert_equal_i(0100644, entry->mode); + cl_assert(!GIT_PERMS_IS_EXEC(entry->mode)); git_commit_free(commit); @@ -1017,7 +1017,7 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert_equal_i(0100755, read_filemode("executable.txt")); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("executable.txt"))); git_commit_free(commit); @@ -1027,7 +1027,7 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert_equal_i(0100644, read_filemode("a/b.txt")); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); git_commit_free(commit); @@ -1037,7 +1037,7 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert_equal_i(0100755, read_filemode("a/b.txt")); + cl_assert(GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); git_commit_free(commit); @@ -1047,7 +1047,7 @@ void test_checkout_tree__filemode_preserved_in_workdir(void) cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid)); cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts)); - cl_assert_equal_i(0100644, read_filemode("a/b.txt")); + cl_assert(!GIT_PERMS_IS_EXEC(read_filemode("a/b.txt"))); git_commit_free(commit); #endif From eea7c850248c04a6ac3aadbb13b2c72c2237013b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 16 Sep 2015 05:44:27 +0000 Subject: [PATCH 072/101] checkout: overwrite files with differing modes When a file exists on disk and we're checking out a file that differs in executableness, remove the old file. This allows us to recreate the new file with p_open, which will take the new mode into account and handle setting the umask properly. Remove any notion of chmod'ing existing files, since it is now handled by the aforementioned removal and was incorrect, as it did not take umask into account. --- src/checkout.c | 15 ++++++--------- tests/checkout/tree.c | 3 ++- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index de48c9e0168..8c06b33353e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -243,6 +243,12 @@ static int checkout_action_common( if (delta->new_file.mode == GIT_FILEMODE_LINK && wd != NULL) *action |= CHECKOUT_ACTION__REMOVE; + /* if the file is on disk and doesn't match our mode, force update */ + if (wd && + GIT_PERMS_IS_EXEC(wd->mode) != + GIT_PERMS_IS_EXEC(delta->new_file.mode)) + *action |= CHECKOUT_ACTION__REMOVE; + notify = GIT_CHECKOUT_NOTIFY_UPDATED; } @@ -1500,15 +1506,6 @@ static int blob_content_to_file( if (error < 0) return error; - if (GIT_PERMS_IS_EXEC(mode)) { - data->perfdata.chmod_calls++; - - if ((error = p_chmod(path, mode)) < 0) { - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", path); - return error; - } - } - if (st) { data->perfdata.stat_calls++; diff --git a/tests/checkout/tree.c b/tests/checkout/tree.c index 5692e1f2566..5680b86df7f 100644 --- a/tests/checkout/tree.c +++ b/tests/checkout/tree.c @@ -996,7 +996,8 @@ mode_t read_filemode(const char *path) git_buf_joinpath(&fullpath, "testrepo", path); cl_must_pass(p_stat(fullpath.ptr, &st)); - result = GIT_PERMS_IS_EXEC(st.st_mode) ? 0100755 : 0100644; + result = GIT_PERMS_IS_EXEC(st.st_mode) ? + GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB; git_buf_free(&fullpath); From ac2fba0ecd68e8eae348dec688cbcd0828432cdf Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 16 Sep 2015 15:07:27 -0400 Subject: [PATCH 073/101] git_futils_mkdir_*: make a relative-to-base mkdir Untangle git_futils_mkdir from git_futils_mkdir_ext - the latter assumes that we own everything beneath the base, as if it were being called with a base of the repository or working directory, and is tailored towards checkout and ensuring that there is no bogosity beneath the base that must be cleaned up. This is (at best) slow and (at worst) unsafe in the larger context of a filesystem where we do not own things and cannot do things like unlink symlinks that are in our way. --- src/checkout.c | 2 +- src/fileops.c | 25 +++++++++++-------- src/fileops.h | 17 ++++++------- src/odb_loose.c | 4 +-- src/refdb_fs.c | 3 ++- src/repository.c | 12 ++++----- tests/checkout/index.c | 2 +- tests/config/global.c | 6 ++--- tests/core/buffer.c | 2 +- tests/core/copy.c | 8 +++--- tests/core/mkdir.c | 54 ++++++++++++++++++++-------------------- tests/core/stat.c | 2 +- tests/index/tests.c | 2 +- tests/odb/alternates.c | 2 +- tests/refs/pack.c | 2 +- tests/repo/discover.c | 16 ++++++------ tests/repo/init.c | 6 ++--- tests/repo/iterator.c | 6 ++--- tests/repo/open.c | 4 +-- tests/status/ignore.c | 14 +++++------ tests/status/worktree.c | 10 ++++---- tests/submodule/status.c | 10 ++++---- 22 files changed, 105 insertions(+), 104 deletions(-) diff --git a/src/checkout.c b/src/checkout.c index 8c06b33353e..2a8bfd55848 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -1366,7 +1366,7 @@ static int checkout_mkdir( mkdir_opts.dir_map = data->mkdir_map; mkdir_opts.pool = &data->pool; - error = git_futils_mkdir_ext( + error = git_futils_mkdir_relative( path, base, mode, flags, &mkdir_opts); data->perfdata.mkdir_calls += mkdir_opts.perfdata.mkdir_calls; diff --git a/src/fileops.c b/src/fileops.c index b7b55159f04..b986a654662 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -18,7 +18,7 @@ GIT__USE_STRMAP int git_futils_mkpath2file(const char *file_path, const mode_t mode) { return git_futils_mkdir( - file_path, NULL, mode, + file_path, mode, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); } @@ -331,8 +331,8 @@ GIT_INLINE(int) validate_existing( return 0; } -int git_futils_mkdir_ext( - const char *path, +int git_futils_mkdir_relative( + const char *relative_path, const char *base, mode_t mode, uint32_t flags, @@ -343,9 +343,13 @@ int git_futils_mkdir_ext( ssize_t root = 0, min_root_len, root_len; char lastch = '/', *tail; struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + + if (!opts) + opts = &empty_opts; /* build path and find "root" where we should start calling mkdir */ - if (git_path_join_unrooted(&make_path, path, base, &root) < 0) + if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) return -1; if (make_path.size == 0) { @@ -503,17 +507,16 @@ int git_futils_mkdir_ext( int git_futils_mkdir( const char *path, - const char *base, mode_t mode, uint32_t flags) { struct git_futils_mkdir_options options = {0}; - return git_futils_mkdir_ext(path, base, mode, flags, &options); + return git_futils_mkdir_relative(path, NULL, mode, flags, &options); } -int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode) +int git_futils_mkdir_r(const char *path, const mode_t mode) { - return git_futils_mkdir(path, base, mode, GIT_MKDIR_PATH); + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); } typedef struct { @@ -777,7 +780,7 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create root directory the first time we need to create a directory */ if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { error = git_futils_mkdir( - info->to_root, NULL, info->dirmode, + info->to_root, info->dirmode, (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; @@ -785,9 +788,9 @@ static int _cp_r_mkdir(cp_r_info *info, git_buf *from) /* create directory with root as base to prevent excess chmods */ if (!error) - error = git_futils_mkdir( + error = git_futils_mkdir_relative( from->ptr + info->from_prefix, info->to_root, - info->dirmode, info->mkdir_flags); + info->dirmode, info->mkdir_flags, NULL); return error; } diff --git a/src/fileops.h b/src/fileops.h index 0f6466c59c2..572ff01a5bf 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -55,12 +55,9 @@ extern int git_futils_creat_locked(const char *path, const mode_t mode); extern int git_futils_creat_locked_withpath(const char *path, const mode_t dirmode, const mode_t mode); /** - * Create a path recursively - * - * If a base parameter is being passed, it's expected to be valued with a - * path pointing to an already existing directory. + * Create a path recursively. */ -extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t mode); +extern int git_futils_mkdir_r(const char *path, const mode_t mode); /** * Flags to pass to `git_futils_mkdir`. @@ -111,20 +108,20 @@ struct git_futils_mkdir_options * and optionally chmods the directory immediately after (or each part of the * path if requested). * - * @param path The path to create. + * @param path The path to create, relative to base. * @param base Root for relative path. These directories will never be made. * @param mode The mode to use for created directories. * @param flags Combination of the mkdir flags above. - * @param opts Extended options, use `git_futils_mkdir` if you are not interested. + * @param opts Extended options, or null. * @return 0 on success, else error code */ -extern int git_futils_mkdir_ext(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); +extern int git_futils_mkdir_relative(const char *path, const char *base, mode_t mode, uint32_t flags, struct git_futils_mkdir_options *opts); /** - * Create a directory or entire path. Similar to `git_futils_mkdir_withperf` + * Create a directory or entire path. Similar to `git_futils_mkdir_relative` * without performance data. */ -extern int git_futils_mkdir(const char *path, const char *base, mode_t mode, uint32_t flags); +extern int git_futils_mkdir(const char *path, mode_t mode, uint32_t flags); /** * Create all the folders required to contain diff --git a/src/odb_loose.c b/src/odb_loose.c index 99b8f7c917b..730c4b1e1d5 100644 --- a/src/odb_loose.c +++ b/src/odb_loose.c @@ -84,9 +84,9 @@ static int object_file_name( static int object_mkdir(const git_buf *name, const loose_backend *be) { - return git_futils_mkdir( + return git_futils_mkdir_relative( name->ptr + be->objects_dirlen, be->objects_dir, be->object_dir_mode, - GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR); + GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST | GIT_MKDIR_VERIFY_DIR, NULL); } static size_t get_binary_object_header(obj_hdr *hdr, git_buf *obj) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index 1ddce464960..921f7862bfc 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -1413,7 +1413,8 @@ static int setup_namespace(git_buf *path, git_repository *repo) git__free(parts); /* Make sure that the folder with the namespace exists */ - if (git_futils_mkdir_r(git_buf_cstr(path), repo->path_repository, 0777) < 0) + if (git_futils_mkdir_relative(git_buf_cstr(path), repo->path_repository, + 0777, GIT_MKDIR_PATH, NULL) < 0) return -1; /* Return root of the namespaced path, i.e. without the trailing '/refs' */ diff --git a/src/repository.c b/src/repository.c index 0f37cfbfe18..d0bf7dc791d 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1441,8 +1441,8 @@ static int repo_init_structure( if (chmod) mkdir_flags |= GIT_MKDIR_CHMOD; - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, mkdir_flags); + error = git_futils_mkdir_relative( + tpl->path, repo_dir, dmode, mkdir_flags, NULL); } else if (!external_tpl) { const char *content = tpl->content; @@ -1464,7 +1464,7 @@ static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2) * don't try to set gid or grant world write access */ return git_futils_mkdir( - buf->ptr, NULL, mode & ~(S_ISGID | 0002), + buf->ptr, mode & ~(S_ISGID | 0002), GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); } @@ -1568,14 +1568,14 @@ static int repo_init_directories( /* create path #4 */ if (wd_path->size > 0 && (error = git_futils_mkdir( - wd_path->ptr, NULL, dirmode & ~S_ISGID, + wd_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR)) < 0) return error; /* create path #2 (if not the same as #4) */ if (!natural_wd && (error = git_futils_mkdir( - repo_path->ptr, NULL, dirmode & ~S_ISGID, + repo_path->ptr, dirmode & ~S_ISGID, GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) return error; } @@ -1585,7 +1585,7 @@ static int repo_init_directories( has_dotgit) { /* create path #1 */ - error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, + error = git_futils_mkdir(repo_path->ptr, dirmode, GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); } diff --git a/tests/checkout/index.c b/tests/checkout/index.c index 0d220e1414e..9fa901867fb 100644 --- a/tests/checkout/index.c +++ b/tests/checkout/index.c @@ -63,7 +63,7 @@ void test_checkout_index__can_remove_untracked_files(void) { git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; - git_futils_mkdir("./testrepo/dir/subdir/subsubdir", NULL, 0755, GIT_MKDIR_PATH); + git_futils_mkdir("./testrepo/dir/subdir/subsubdir", 0755, GIT_MKDIR_PATH); cl_git_mkfile("./testrepo/dir/one", "one\n"); cl_git_mkfile("./testrepo/dir/subdir/two", "two\n"); diff --git a/tests/config/global.c b/tests/config/global.c index 4481308d64b..b5e83fec04f 100644 --- a/tests/config/global.c +++ b/tests/config/global.c @@ -6,17 +6,17 @@ void test_config_global__initialize(void) { git_buf path = GIT_BUF_INIT; - cl_git_pass(git_futils_mkdir_r("home", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("home", 0777)); cl_git_pass(git_path_prettify(&path, "home", NULL)); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); - cl_git_pass(git_futils_mkdir_r("xdg/git", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("xdg/git", 0777)); cl_git_pass(git_path_prettify(&path, "xdg/git", NULL)); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, path.ptr)); - cl_git_pass(git_futils_mkdir_r("etc", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("etc", 0777)); cl_git_pass(git_path_prettify(&path, "etc", NULL)); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, path.ptr)); diff --git a/tests/core/buffer.c b/tests/core/buffer.c index 0e7026a9c1b..9872af7f400 100644 --- a/tests/core/buffer.c +++ b/tests/core/buffer.c @@ -929,7 +929,7 @@ void test_core_buffer__similarity_metric(void) cl_git_pass(git_buf_sets(&buf, SIMILARITY_TEST_DATA_1)); cl_git_pass(git_hashsig_create(&a, buf.ptr, buf.size, GIT_HASHSIG_NORMAL)); - cl_git_pass(git_futils_mkdir("scratch", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("scratch", 0755, GIT_MKDIR_PATH)); cl_git_mkfile("scratch/testdata", SIMILARITY_TEST_DATA_1); cl_git_pass(git_hashsig_create_fromfile( &b, "scratch/testdata", GIT_HASHSIG_NORMAL)); diff --git a/tests/core/copy.c b/tests/core/copy.c index 04b2dfab5e7..967748cc5e0 100644 --- a/tests/core/copy.c +++ b/tests/core/copy.c @@ -25,7 +25,7 @@ void test_core_copy__file_in_dir(void) struct stat st; const char *content = "This is some other stuff to copy\n"; - cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("an_dir/in_a_dir", 0775, GIT_MKDIR_PATH)); cl_git_mkfile("an_dir/in_a_dir/copy_me", content); cl_assert(git_path_isdir("an_dir")); @@ -60,9 +60,9 @@ void test_core_copy__tree(void) struct stat st; const char *content = "File content\n"; - cl_git_pass(git_futils_mkdir("src/b", NULL, 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/d", NULL, 0775, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("src/c/e", NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/b", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/d", 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("src/c/e", 0775, GIT_MKDIR_PATH)); cl_git_mkfile("src/f1", content); cl_git_mkfile("src/b/f2", content); diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index f76fe1da9fc..e435a9a6454 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -19,37 +19,37 @@ void test_core_mkdir__basic(void) /* make a directory */ cl_assert(!git_path_isdir("d0")); - cl_git_pass(git_futils_mkdir("d0", NULL, 0755, 0)); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); cl_assert(git_path_isdir("d0")); /* make a path */ cl_assert(!git_path_isdir("d1")); - cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("d1/d1.1/d1.2", 0755, GIT_MKDIR_PATH)); cl_assert(git_path_isdir("d1")); cl_assert(git_path_isdir("d1/d1.1")); cl_assert(git_path_isdir("d1/d1.1/d1.2")); /* make a dir exclusively */ cl_assert(!git_path_isdir("d2")); - cl_git_pass(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); cl_assert(git_path_isdir("d2")); /* make exclusive failure */ - cl_git_fail(git_futils_mkdir("d2", NULL, 0755, GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir("d2", 0755, GIT_MKDIR_EXCL)); /* make a path exclusively */ cl_assert(!git_path_isdir("d3")); - cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); cl_assert(git_path_isdir("d3")); cl_assert(git_path_isdir("d3/d3.1/d3.2")); /* make exclusive path failure */ - cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", NULL, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir("d3/d3.1/d3.2", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); /* ??? Should EXCL only apply to the last item in the path? */ /* path with trailing slash? */ cl_assert(!git_path_isdir("d4")); - cl_git_pass(git_futils_mkdir("d4/d4.1/", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("d4/d4.1/", 0755, GIT_MKDIR_PATH)); cl_assert(git_path_isdir("d4/d4.1")); } @@ -65,38 +65,38 @@ void test_core_mkdir__with_base(void) cl_set_cleanup(cleanup_basedir, NULL); - cl_git_pass(git_futils_mkdir(BASEDIR, NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(BASEDIR, 0755, GIT_MKDIR_PATH)); - cl_git_pass(git_futils_mkdir("a", BASEDIR, 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("a", BASEDIR, 0755, 0, NULL)); cl_assert(git_path_isdir(BASEDIR "/a")); - cl_git_pass(git_futils_mkdir("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("b/b1/b2", BASEDIR, 0755, GIT_MKDIR_PATH, NULL)); cl_assert(git_path_isdir(BASEDIR "/b/b1/b2")); /* exclusive with existing base */ - cl_git_pass(git_futils_mkdir("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir_relative("c/c1/c2", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: exclusive with duplicated suffix */ - cl_git_fail(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: exclusive with any duplicated component */ - cl_git_fail(git_futils_mkdir("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("c/cz/cz", BASEDIR, 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* success: exclusive without path */ - cl_git_pass(git_futils_mkdir("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL)); + cl_git_pass(git_futils_mkdir_relative("c/c1/c3", BASEDIR, 0755, GIT_MKDIR_EXCL, NULL)); /* path with shorter base and existing dirs */ - cl_git_pass(git_futils_mkdir("dir/here/d/", "base", 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("dir/here/d/", "base", 0755, GIT_MKDIR_PATH, NULL)); cl_assert(git_path_isdir("base/dir/here/d")); /* fail: path with shorter base and existing dirs */ - cl_git_fail(git_futils_mkdir("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL)); + cl_git_fail(git_futils_mkdir_relative("dir/here/e/", "base", 0755, GIT_MKDIR_PATH | GIT_MKDIR_EXCL, NULL)); /* fail: base with missing components */ - cl_git_fail(git_futils_mkdir("f/", "base/missing", 0755, GIT_MKDIR_PATH)); + cl_git_fail(git_futils_mkdir_relative("f/", "base/missing", 0755, GIT_MKDIR_PATH, NULL)); /* success: shift missing component to path */ - cl_git_pass(git_futils_mkdir("missing/f/", "base/", 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("missing/f/", "base/", 0755, GIT_MKDIR_PATH, NULL)); } static void cleanup_chmod_root(void *ref) @@ -135,9 +135,9 @@ void test_core_mkdir__chmods(void) cl_set_cleanup(cleanup_chmod_root, old); - cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0)); + cl_git_pass(git_futils_mkdir("r", 0777, 0)); - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -146,7 +146,7 @@ void test_core_mkdir__chmods(void) cl_git_pass(git_path_lstat("r/mode/is/important", &st)); check_mode(0755, st.st_mode); - cl_git_pass(git_futils_mkdir("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); cl_git_pass(git_path_lstat("r/mode2", &st)); check_mode(0755, st.st_mode); @@ -155,7 +155,7 @@ void test_core_mkdir__chmods(void) cl_git_pass(git_path_lstat("r/mode2/is2/important2", &st)); check_mode(0777, st.st_mode); - cl_git_pass(git_futils_mkdir("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode3/is3/important3", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode3", &st)); check_mode(0777, st.st_mode); @@ -166,7 +166,7 @@ void test_core_mkdir__chmods(void) /* test that we chmod existing dir */ - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -177,7 +177,7 @@ void test_core_mkdir__chmods(void) /* test that we chmod even existing dirs if CHMOD_PATH is set */ - cl_git_pass(git_futils_mkdir("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH)); + cl_git_pass(git_futils_mkdir_relative("mode2/is2/important2.1", "r", 0777, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode2", &st)); check_mode(0777, st.st_mode); @@ -200,8 +200,8 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) *old = p_umask(022); cl_set_cleanup(cleanup_chmod_root, old); - cl_git_pass(git_futils_mkdir("r", NULL, 0777, 0)); - cl_git_pass(git_futils_mkdir("mode/is/important", "r", 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("r", 0777, 0)); + cl_git_pass(git_futils_mkdir_relative("mode/is/important", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode", &st)); check_mode(0755, st.st_mode); @@ -210,7 +210,7 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) check_mode(0111, st.st_mode); cl_git_pass( - git_futils_mkdir("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH)); + git_futils_mkdir_relative("mode/is/okay/inside", "r", 0777, GIT_MKDIR_PATH, NULL)); cl_git_pass(git_path_lstat("r/mode/is/okay/inside", &st)); check_mode(0755, st.st_mode); diff --git a/tests/core/stat.c b/tests/core/stat.c index bd9b990e324..ef2e45a15bb 100644 --- a/tests/core/stat.c +++ b/tests/core/stat.c @@ -5,7 +5,7 @@ void test_core_stat__initialize(void) { - cl_git_pass(git_futils_mkdir("root/d1/d2", NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("root/d1/d2", 0755, GIT_MKDIR_PATH)); cl_git_mkfile("root/file", "whatever\n"); cl_git_mkfile("root/d1/file", "whatever\n"); } diff --git a/tests/index/tests.c b/tests/index/tests.c index 2a416fc7b2d..1498196b2d8 100644 --- a/tests/index/tests.c +++ b/tests/index/tests.c @@ -752,7 +752,7 @@ void test_index_tests__reload_from_disk(void) cl_set_cleanup(&cleanup_myrepo, NULL); - cl_git_pass(git_futils_mkdir("./myrepo", NULL, 0777, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir("./myrepo", 0777, GIT_MKDIR_PATH)); cl_git_mkfile("./myrepo/a.txt", "a\n"); cl_git_mkfile("./myrepo/b.txt", "b\n"); diff --git a/tests/odb/alternates.c b/tests/odb/alternates.c index c75f6feaae4..b5c0e79c0a9 100644 --- a/tests/odb/alternates.c +++ b/tests/odb/alternates.c @@ -29,7 +29,7 @@ static void init_linked_repo(const char *path, const char *alternate) cl_git_pass(git_path_prettify(&destpath, alternate, NULL)); cl_git_pass(git_buf_joinpath(&destpath, destpath.ptr, "objects")); cl_git_pass(git_buf_joinpath(&filepath, git_repository_path(repo), "objects/info")); - cl_git_pass(git_futils_mkdir(filepath.ptr, NULL, 0755, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(filepath.ptr, 0755, GIT_MKDIR_PATH)); cl_git_pass(git_buf_joinpath(&filepath, filepath.ptr , "alternates")); cl_git_pass(git_filebuf_open(&file, git_buf_cstr(&filepath), 0, 0666)); diff --git a/tests/refs/pack.c b/tests/refs/pack.c index 7dfaf6d8f85..bda86f69a98 100644 --- a/tests/refs/pack.c +++ b/tests/refs/pack.c @@ -36,7 +36,7 @@ void test_refs_pack__empty(void) git_buf temp_path = GIT_BUF_INIT; cl_git_pass(git_buf_join_n(&temp_path, '/', 3, git_repository_path(g_repo), GIT_REFS_HEADS_DIR, "empty_dir")); - cl_git_pass(git_futils_mkdir_r(temp_path.ptr, NULL, GIT_REFS_DIR_MODE)); + cl_git_pass(git_futils_mkdir_r(temp_path.ptr, GIT_REFS_DIR_MODE)); git_buf_free(&temp_path); packall(); diff --git a/tests/repo/discover.c b/tests/repo/discover.c index 7904b6496bb..86bd7458fee 100644 --- a/tests/repo/discover.c +++ b/tests/repo/discover.c @@ -77,7 +77,7 @@ void test_repo_discover__0(void) const char *ceiling_dirs; const mode_t mode = 0777; - git_futils_mkdir_r(DISCOVER_FOLDER, NULL, mode); + git_futils_mkdir_r(DISCOVER_FOLDER, mode); append_ceiling_dir(&ceiling_dirs_buf, TEMP_REPO_FOLDER); ceiling_dirs = git_buf_cstr(&ceiling_dirs_buf); @@ -88,15 +88,15 @@ void test_repo_discover__0(void) 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, NULL, mode)); + 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, NULL, mode)); + 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, NULL, mode)); + 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: ../../../../"); @@ -105,13 +105,13 @@ void test_repo_discover__0(void) ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB, ceiling_dirs, &sub_repository_path); ensure_repository_discover(REPOSITORY_ALTERNATE_FOLDER_SUB_SUB_SUB, ceiling_dirs, &repository_path); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER1, mode)); write_file(ALTERNATE_MALFORMED_FOLDER1 "/" DOT_GIT, "Anything but not gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER2, mode)); write_file(ALTERNATE_MALFORMED_FOLDER2 "/" DOT_GIT, "gitdir:"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, NULL, mode)); + cl_git_pass(git_futils_mkdir_r(ALTERNATE_MALFORMED_FOLDER3, mode)); write_file(ALTERNATE_MALFORMED_FOLDER3 "/" DOT_GIT, "gitdir: \n\n\n"); - cl_git_pass(git_futils_mkdir_r(ALTERNATE_NOT_FOUND_FOLDER, NULL, mode)); + 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)); diff --git a/tests/repo/init.c b/tests/repo/init.c index 929d7418004..7a9ec20f182 100644 --- a/tests/repo/init.c +++ b/tests/repo/init.c @@ -99,7 +99,7 @@ void test_repo_init__bare_repo_escaping_current_workdir(void) cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL)); cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c")); - cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE)); + cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), GIT_DIR_MODE)); /* Change the current working directory */ cl_git_pass(chdir(git_buf_cstr(&path_repository))); @@ -312,7 +312,7 @@ void test_repo_init__extended_0(void) cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts)); /* make the directory first, then it should succeed */ - cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts)); cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/")); @@ -631,7 +631,7 @@ void test_repo_init__can_reinit_an_initialized_repository(void) cl_set_cleanup(&cleanup_repository, "extended"); - cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0)); + cl_git_pass(git_futils_mkdir("extended", 0775, 0)); cl_git_pass(git_repository_init(&_repo, "extended", false)); cl_git_pass(git_repository_init(&reinit, "extended", false)); diff --git a/tests/repo/iterator.c b/tests/repo/iterator.c index 9c4cc9ea4f3..83b824691cf 100644 --- a/tests/repo/iterator.c +++ b/tests/repo/iterator.c @@ -848,14 +848,14 @@ static void build_workdir_tree(const char *root, int dirs, int subs) 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, NULL, 0775, GIT_MKDIR_PATH)); + 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, NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(buf, 0775, GIT_MKDIR_PATH)); } for (j = 0; j < subs; ++j) { @@ -865,7 +865,7 @@ static void build_workdir_tree(const char *root, int dirs, int subs) 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, NULL, 0775, GIT_MKDIR_PATH)); + cl_git_pass(git_futils_mkdir(sub, 0775, GIT_MKDIR_PATH)); if (j % 2 == 0) { size_t sublen = strlen(sub); diff --git a/tests/repo/open.c b/tests/repo/open.c index eb459e51d6d..d3d087231a7 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -91,7 +91,7 @@ static void make_gitlink_dir(const char *dir, const char *linktext) { git_buf path = GIT_BUF_INIT; - cl_git_pass(git_futils_mkdir(dir, NULL, 0777, GIT_MKDIR_VERIFY_DIR)); + cl_git_pass(git_futils_mkdir(dir, 0777, GIT_MKDIR_VERIFY_DIR)); cl_git_pass(git_buf_joinpath(&path, dir, ".git")); cl_git_rewritefile(path.ptr, linktext); git_buf_free(&path); @@ -222,7 +222,7 @@ void test_repo_open__bad_gitlinks(void) cl_git_sandbox_init("attr"); cl_git_pass(p_mkdir("invalid", 0777)); - cl_git_pass(git_futils_mkdir_r("invalid2/.git", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("invalid2/.git", 0777)); for (scan = bad_links; *scan != NULL; scan++) { make_gitlink_dir("alternate", *scan); diff --git a/tests/status/ignore.c b/tests/status/ignore.c index bbf8f491193..c318046dad8 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -148,7 +148,7 @@ void test_status_ignore__ignore_pattern_contains_space(void) cl_git_pass(git_status_file(&flags, g_repo, "foo bar.txt")); cl_assert(flags == GIT_STATUS_IGNORED); - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", NULL, mode)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/foo", mode)); cl_git_mkfile("empty_standard_repo/foo/look-ma.txt", "I'm not going to be ignored!"); cl_git_pass(git_status_file(&flags, g_repo, "foo/look-ma.txt")); @@ -206,7 +206,7 @@ void test_status_ignore__subdirectories(void) * used a rooted path for an ignore, so I changed this behavior. */ cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/test/ignore_me", NULL, 0775)); + "empty_standard_repo/test/ignore_me", 0775)); cl_git_mkfile( "empty_standard_repo/test/ignore_me/file", "I'm going to be ignored!"); cl_git_mkfile( @@ -230,9 +230,9 @@ static void make_test_data(const char *reponame, const char **files) g_repo = cl_git_sandbox_init(reponame); for (scan = files; *scan != NULL; ++scan) { - cl_git_pass(git_futils_mkdir( + cl_git_pass(git_futils_mkdir_relative( *scan + repolen, reponame, - 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST)); + 0777, GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST, NULL)); cl_git_mkfile(*scan, "contents"); } } @@ -612,7 +612,7 @@ void test_status_ignore__issue_1766_negated_ignores(void) g_repo = cl_git_sandbox_init("empty_standard_repo"); cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/a", NULL, 0775)); + "empty_standard_repo/a", 0775)); cl_git_mkfile( "empty_standard_repo/a/.gitignore", "*\n!.gitignore\n"); cl_git_mkfile( @@ -622,7 +622,7 @@ void test_status_ignore__issue_1766_negated_ignores(void) assert_is_ignored("a/ignoreme"); cl_git_pass(git_futils_mkdir_r( - "empty_standard_repo/b", NULL, 0775)); + "empty_standard_repo/b", 0775)); cl_git_mkfile( "empty_standard_repo/b/.gitignore", "*\n!.gitignore\n"); cl_git_mkfile( @@ -1033,7 +1033,7 @@ void test_status_ignore__negate_starstar(void) "code/projects/**/packages/*\n" "!code/projects/**/packages/repositories.config"); - cl_git_pass(git_futils_mkdir_r("code/projects/foo/bar/packages", "empty_standard_repo", 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/code/projects/foo/bar/packages", 0777)); cl_git_mkfile("empty_standard_repo/code/projects/foo/bar/packages/repositories.config", ""); cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "code/projects/foo/bar/packages/repositories.config")); diff --git a/tests/status/worktree.c b/tests/status/worktree.c index 75c7b71b079..fc4afc6beaa 100644 --- a/tests/status/worktree.c +++ b/tests/status/worktree.c @@ -195,7 +195,7 @@ void test_status_worktree__swap_subdir_with_recurse_and_pathspec(void) cl_git_pass(p_rename("status/subdir", "status/current_file")); cl_git_pass(p_rename("status/swap", "status/subdir")); cl_git_mkfile("status/.new_file", "dummy"); - cl_git_pass(git_futils_mkdir_r("status/zzz_new_dir", NULL, 0777)); + 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"); @@ -917,7 +917,7 @@ void test_status_worktree__long_filenames(void) // Create directory with amazingly long filename sprintf(path, "empty_standard_repo/%s", longname); - cl_git_pass(git_futils_mkdir_r(path, NULL, 0777)); + cl_git_pass(git_futils_mkdir_r(path, 0777)); sprintf(path, "empty_standard_repo/%s/foo", longname); cl_git_mkfile(path, "dummy"); @@ -1007,7 +1007,7 @@ void test_status_worktree__unreadable(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); @@ -1041,7 +1041,7 @@ void test_status_worktree__unreadable_not_included(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); @@ -1074,7 +1074,7 @@ void test_status_worktree__unreadable_as_untracked(void) status_entry_counts counts = {0}; /* Create directory with no read permission */ - cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", NULL, 0777)); + cl_git_pass(git_futils_mkdir_r("empty_standard_repo/no_permission", 0777)); cl_git_mkfile("empty_standard_repo/no_permission/foo", "dummy"); p_chmod("empty_standard_repo/no_permission", 0644); diff --git a/tests/submodule/status.c b/tests/submodule/status.c index 5f4e620531c..10f385ce9b4 100644 --- a/tests/submodule/status.c +++ b/tests/submodule/status.c @@ -92,7 +92,7 @@ void test_submodule_status__ignore_none(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); status = get_submodule_status(g_repo, "sm_unchanged"); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -141,7 +141,7 @@ void test_submodule_status__ignore_untracked(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -185,7 +185,7 @@ void test_submodule_status__ignore_dirty(void) cl_assert((status & GIT_SUBMODULE_STATUS_WD_DELETED) != 0); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert((status & GIT_SUBMODULE_STATUS_WD_UNINITIALIZED) != 0); @@ -229,7 +229,7 @@ void test_submodule_status__ignore_all(void) cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); /* now mkdir sm_unchanged to test uninitialized */ - cl_git_pass(git_futils_mkdir("sm_unchanged", "submod2", 0755, 0)); + cl_git_pass(git_futils_mkdir_relative("sm_unchanged", "submod2", 0755, 0, NULL)); cl_git_pass(git_submodule_status(&status, g_repo,"sm_unchanged", ign)); cl_assert(GIT_SUBMODULE_STATUS_IS_UNMODIFIED(status)); @@ -338,7 +338,7 @@ void test_submodule_status__untracked_dirs_containing_ignored_files(void) "submod2/.git/modules/sm_unchanged/info/exclude", "\n*.ignored\n"); cl_git_pass( - git_futils_mkdir("sm_unchanged/directory", "submod2", 0755, 0)); + git_futils_mkdir_relative("sm_unchanged/directory", "submod2", 0755, 0, NULL)); cl_git_mkfile( "submod2/sm_unchanged/directory/i_am.ignored", "ignore this file, please\n"); From 08df66301ec677f6cd98175a914dc933e2fb43a9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 16 Sep 2015 18:07:56 -0400 Subject: [PATCH 074/101] core::mkdir tests: include absolute mkdirs --- tests/core/mkdir.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index e435a9a6454..11fecb118b9 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -13,6 +13,41 @@ static void cleanup_basic_dirs(void *ref) git_futils_rmdir_r("d4", NULL, GIT_RMDIR_EMPTY_HIERARCHY); } +void test_core_mkdir__absolute(void) +{ + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + git_buf_joinpath(&path, clar_sandbox_path(), "d0"); + + /* make a directory */ + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_path_isdir(path.ptr)); + + git_buf_joinpath(&path, path.ptr, "subdir"); + + /* make a directory */ + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(git_path_isdir(path.ptr)); + + git_buf_joinpath(&path, path.ptr, "another"); + + /* make a directory */ + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_path_isdir(path.ptr)); + + git_buf_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); + + /* make a directory */ + cl_assert(!git_path_isdir(path.ptr)); + cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); + cl_assert(git_path_isdir(path.ptr)); +} + void test_core_mkdir__basic(void) { cl_set_cleanup(cleanup_basic_dirs, NULL); From 0862ec2eb9529573ab46f3975defc0b7632bede4 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 09:58:38 -0400 Subject: [PATCH 075/101] core::mkdir tests: ensure we don't stomp symlinks in mkdir In `mkdir` and `mkdir_r`, ensure that we don't try to remove symlinks that are in our way. --- src/path.c | 11 +++++++++++ src/path.h | 6 ++++++ tests/core/mkdir.c | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/src/path.c b/src/path.c index cb11acee3e1..72cb289e0b3 100644 --- a/src/path.c +++ b/src/path.c @@ -526,6 +526,17 @@ bool git_path_isfile(const char *path) return S_ISREG(st.st_mode) != 0; } +bool git_path_islink(const char *path) +{ + struct stat st; + + assert(path); + if (p_lstat(path, &st) < 0) + return false; + + return S_ISLNK(st.st_mode) != 0; +} + #ifdef GIT_WIN32 bool git_path_is_empty_dir(const char *path) diff --git a/src/path.h b/src/path.h index 971603ea7fa..c76e90343e2 100644 --- a/src/path.h +++ b/src/path.h @@ -168,6 +168,12 @@ extern bool git_path_isdir(const char *path); */ extern bool git_path_isfile(const char *path); +/** + * Check if the given path points to a symbolic link. + * @return true or false + */ +extern bool git_path_islink(const char *path); + /** * Check if the given path is a directory, and is empty. */ diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index 11fecb118b9..8d487e5942b 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -222,6 +222,40 @@ void test_core_mkdir__chmods(void) check_mode(0777, st.st_mode); } +void test_core_mkdir__keeps_parent_symlinks(void) +{ +#ifndef GIT_WIN32 + git_buf path = GIT_BUF_INIT; + + cl_set_cleanup(cleanup_basic_dirs, NULL); + + /* make a directory */ + cl_assert(!git_path_isdir("d0")); + cl_git_pass(git_futils_mkdir("d0", 0755, 0)); + cl_assert(git_path_isdir("d0")); + + cl_must_pass(symlink("d0", "d1")); + cl_assert(git_path_islink("d1")); + + cl_git_pass(git_futils_mkdir("d1/foo/bar", 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_path_islink("d1")); + cl_assert(git_path_isdir("d1/foo/bar")); + cl_assert(git_path_isdir("d0/foo/bar")); + + cl_must_pass(symlink("d0", "d2")); + cl_assert(git_path_islink("d2")); + + git_buf_joinpath(&path, clar_sandbox_path(), "d2/other/dir"); + + cl_git_pass(git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_PATH|GIT_MKDIR_REMOVE_SYMLINKS)); + cl_assert(git_path_islink("d2")); + cl_assert(git_path_isdir("d2/other/dir")); + cl_assert(git_path_isdir("d0/other/dir")); + + git_buf_free(&path); +#endif +} + void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) { struct stat st; From e24c60dba4cd7c6b768fd6c39d4a0c003a48fb1f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 09:42:05 -0400 Subject: [PATCH 076/101] mkdir: find component paths for mkdir_relative `git_futils_mkdir` does not blindly call `git_futils_mkdir_relative`. `git_futils_mkdir_relative` is used when you have some base directory and want to create some path inside of it, potentially removing blocking symlinks and files in the process. This is not suitable for a general recursive mkdir within the filesystem. Instead, when `mkdir` is being recursive, locate the first existent parent directory and use that as the base for `mkdir_relative`. --- src/fileops.c | 178 ++++++++++++++++++++++++++++++++++----------- tests/core/mkdir.c | 15 ++-- 2 files changed, 142 insertions(+), 51 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index b986a654662..f00c3e82193 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -331,59 +331,163 @@ GIT_INLINE(int) validate_existing( return 0; } -int git_futils_mkdir_relative( - const char *relative_path, - const char *base, - mode_t mode, - uint32_t flags, - struct git_futils_mkdir_options *opts) + +GIT_INLINE(int) mkdir_canonicalize( + git_buf *path, + uint32_t flags) { - int error = -1; - git_buf make_path = GIT_BUF_INIT; - ssize_t root = 0, min_root_len, root_len; - char lastch = '/', *tail; - struct stat st; - struct git_futils_mkdir_options empty_opts = {0}; - - if (!opts) - opts = &empty_opts; + ssize_t root_len; - /* build path and find "root" where we should start calling mkdir */ - if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + if (path->size == 0) { + giterr_set(GITERR_OS, "attempt to create empty path"); return -1; - - if (make_path.size == 0) { - giterr_set(GITERR_OS, "Attempt to create empty path"); - goto done; } /* Trim trailing slashes (except the root) */ - if ((root_len = git_path_root(make_path.ptr)) < 0) + if ((root_len = git_path_root(path->ptr)) < 0) root_len = 0; else root_len++; - while (make_path.size > (size_t)root_len && - make_path.ptr[make_path.size - 1] == '/') - make_path.ptr[--make_path.size] = '\0'; + while (path->size > (size_t)root_len && path->ptr[path->size - 1] == '/') + path->ptr[--path->size] = '\0'; /* if we are not supposed to made the last element, truncate it */ if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); + git_path_dirname_r(path, path->ptr); flags |= GIT_MKDIR_SKIP_LAST; } if ((flags & GIT_MKDIR_SKIP_LAST) != 0) { - git_path_dirname_r(&make_path, make_path.ptr); + git_path_dirname_r(path, path->ptr); } /* We were either given the root path (or trimmed it to - * the root), we don't have anything to do. + * the root), we don't have anything to do. + */ + if (path->size <= (size_t)root_len) + git_buf_clear(path); + + return 0; +} + +int git_futils_mkdir( + const char *path, + mode_t mode, + uint32_t flags) +{ + git_buf make_path = GIT_BUF_INIT, parent_path = GIT_BUF_INIT; + const char *relative; + struct git_futils_mkdir_options opts = { 0 }; + struct stat st; + size_t depth = 0; + int len = 0, error; + + if ((error = git_buf_puts(&make_path, path)) < 0 || + (error = mkdir_canonicalize(&make_path, flags)) < 0 || + (error = git_buf_puts(&parent_path, make_path.ptr)) < 0 || + make_path.size == 0) + goto done; + + /* find the first parent directory that exists. this will be used + * as the base to dirname_relative. */ - if (make_path.size <= (size_t)root_len) { - error = 0; + for (relative = make_path.ptr; parent_path.size; ) { + error = p_lstat(parent_path.ptr, &st); + + if (error == 0) { + break; + } else if (errno != ENOENT) { + giterr_set(GITERR_OS, "failed to stat '%s'", parent_path.ptr); + goto done; + } + + depth++; + + /* examine the parent of the current path */ + if ((len = git_path_dirname_r(&parent_path, parent_path.ptr)) < 0) { + error = len; + goto done; + } + + assert(len); + + /* we've walked all the given path's parents and it's either relative + * or rooted. either way, give up and make the entire path. + */ + if (len == 1 && + (parent_path.ptr[0] == '.' || parent_path.ptr[0] == '/')) { + relative = make_path.ptr; + break; + } + + relative = make_path.ptr + len + 1; + + /* not recursive? just make this directory relative to its parent. */ + if ((flags & GIT_MKDIR_PATH) == 0) + break; + } + + /* we found an item at the location we're trying to create, + * validate it. + */ + if (depth == 0) { + if ((error = validate_existing(make_path.ptr, &st, mode, flags, &opts.perfdata)) < 0) + goto done; + + if ((flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_FILESYSTEM, "failed to make directory '%s': " + "directory exists", make_path.ptr); + error = GIT_EEXISTS; + goto done; + } + goto done; } + /* we already took `SKIP_LAST` and `SKIP_LAST2` into account when + * canonicalizing `make_path`. + */ + flags &= ~(GIT_MKDIR_SKIP_LAST2 | GIT_MKDIR_SKIP_LAST); + + error = git_futils_mkdir_relative(relative, + parent_path.size ? parent_path.ptr : NULL, mode, flags, &opts); + +done: + git_buf_free(&make_path); + git_buf_free(&parent_path); + return error; +} + +int git_futils_mkdir_r(const char *path, const mode_t mode) +{ + return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); +} + +int git_futils_mkdir_relative( + const char *relative_path, + const char *base, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + git_buf make_path = GIT_BUF_INIT; + ssize_t root = 0, min_root_len; + char lastch = '/', *tail; + struct stat st; + struct git_futils_mkdir_options empty_opts = {0}; + int error; + + if (!opts) + opts = &empty_opts; + + /* build path and find "root" where we should start calling mkdir */ + if (git_path_join_unrooted(&make_path, relative_path, base, &root) < 0) + return -1; + + if ((error = mkdir_canonicalize(&make_path, flags)) < 0 || + make_path.size == 0) + goto done; + /* if we are not supposed to make the whole path, reset root */ if ((flags & GIT_MKDIR_PATH) == 0) root = git_buf_rfind(&make_path, '/'); @@ -505,20 +609,6 @@ int git_futils_mkdir_relative( return error; } -int git_futils_mkdir( - const char *path, - mode_t mode, - uint32_t flags) -{ - struct git_futils_mkdir_options options = {0}; - return git_futils_mkdir_relative(path, NULL, mode, flags, &options); -} - -int git_futils_mkdir_r(const char *path, const mode_t mode) -{ - return git_futils_mkdir(path, mode, GIT_MKDIR_PATH); -} - typedef struct { const char *base; size_t baselen; diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index 8d487e5942b..5e6a060021a 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -27,25 +27,27 @@ void test_core_mkdir__absolute(void) cl_assert(git_path_isdir(path.ptr)); git_buf_joinpath(&path, path.ptr, "subdir"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir(path.ptr, 0755, 0)); cl_assert(git_path_isdir(path.ptr)); + /* ensure mkdir_r works for a single subdir */ git_buf_joinpath(&path, path.ptr, "another"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); cl_assert(git_path_isdir(path.ptr)); + /* ensure mkdir_r works */ git_buf_joinpath(&path, clar_sandbox_path(), "d1/foo/bar/asdf"); - - /* make a directory */ cl_assert(!git_path_isdir(path.ptr)); cl_git_pass(git_futils_mkdir_r(path.ptr, 0755)); cl_assert(git_path_isdir(path.ptr)); + + /* ensure we don't imply recursive */ + git_buf_joinpath(&path, clar_sandbox_path(), "d2/foo/bar/asdf"); + cl_assert(!git_path_isdir(path.ptr)); + cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); + cl_assert(!git_path_isdir(path.ptr)); } void test_core_mkdir__basic(void) @@ -285,4 +287,3 @@ void test_core_mkdir__mkdir_path_inside_unwriteable_parent(void) cl_must_pass(p_chmod("r/mode", 0777)); } - From 81aaf3704a2728b0478c0e9f347b2a8c61152081 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 11:26:38 -0400 Subject: [PATCH 077/101] mkdir: chmod existing paths with `GIT_MKDIR_CHMOD` --- src/fileops.c | 91 +++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 42 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index f00c3e82193..739a98fc404 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -289,48 +289,76 @@ void git_futils_mmap_free(git_map *out) p_munmap(out); } -GIT_INLINE(int) validate_existing( - const char *make_path, +GIT_INLINE(int) mkdir_validate_dir( + const char *path, struct stat *st, mode_t mode, uint32_t flags, - struct git_futils_mkdir_perfdata *perfdata) + struct git_futils_mkdir_options *opts) { + /* with exclusive create, existing dir is an error */ + if ((flags & GIT_MKDIR_EXCL) != 0) { + giterr_set(GITERR_FILESYSTEM, + "Failed to make directory '%s': directory exists", path); + return GIT_EEXISTS; + } + if ((S_ISREG(st->st_mode) && (flags & GIT_MKDIR_REMOVE_FILES)) || (S_ISLNK(st->st_mode) && (flags & GIT_MKDIR_REMOVE_SYMLINKS))) { - if (p_unlink(make_path) < 0) { + if (p_unlink(path) < 0) { giterr_set(GITERR_OS, "Failed to remove %s '%s'", - S_ISLNK(st->st_mode) ? "symlink" : "file", make_path); + S_ISLNK(st->st_mode) ? "symlink" : "file", path); return GIT_EEXISTS; } - perfdata->mkdir_calls++; + opts->perfdata.mkdir_calls++; - if (p_mkdir(make_path, mode) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_mkdir(path, mode) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (S_ISLNK(st->st_mode)) { /* Re-stat the target, make sure it's a directory */ - perfdata->stat_calls++; + opts->perfdata.stat_calls++; - if (p_stat(make_path, st) < 0) { - giterr_set(GITERR_OS, "Failed to make directory '%s'", make_path); + if (p_stat(path, st) < 0) { + giterr_set(GITERR_OS, "Failed to make directory '%s'", path); return GIT_EEXISTS; } } else if (!S_ISDIR(st->st_mode)) { giterr_set(GITERR_FILESYSTEM, - "Failed to make directory '%s': directory exists", make_path); + "Failed to make directory '%s': directory exists", path); return GIT_EEXISTS; } return 0; } +GIT_INLINE(int) mkdir_validate_mode( + const char *path, + struct stat *st, + bool terminal_path, + mode_t mode, + uint32_t flags, + struct git_futils_mkdir_options *opts) +{ + if (((terminal_path && (flags & GIT_MKDIR_CHMOD) != 0) || + (flags & GIT_MKDIR_CHMOD_PATH) != 0) && st->st_mode != mode) { + + opts->perfdata.chmod_calls++; + + if (p_chmod(path, mode) < 0) { + giterr_set(GITERR_OS, "failed to set permissions on '%s'", path); + return -1; + } + } + + return 0; +} GIT_INLINE(int) mkdir_canonicalize( git_buf *path, @@ -431,15 +459,11 @@ int git_futils_mkdir( * validate it. */ if (depth == 0) { - if ((error = validate_existing(make_path.ptr, &st, mode, flags, &opts.perfdata)) < 0) - goto done; + error = mkdir_validate_dir(make_path.ptr, &st, mode, flags, &opts); - if ((flags & GIT_MKDIR_EXCL) != 0) { - giterr_set(GITERR_FILESYSTEM, "failed to make directory '%s': " - "directory exists", make_path.ptr); - error = GIT_EEXISTS; - goto done; - } + if (!error) + error = mkdir_validate_mode( + make_path.ptr, &st, true, mode, flags, &opts); goto done; } @@ -545,32 +569,15 @@ int git_futils_mkdir_relative( goto done; } } else { - /* with exclusive create, existing dir is an error */ - if ((flags & GIT_MKDIR_EXCL) != 0) { - giterr_set(GITERR_FILESYSTEM, "Failed to make directory '%s': directory exists", make_path.ptr); - error = GIT_EEXISTS; + if ((error = mkdir_validate_dir( + make_path.ptr, &st, mode, flags, opts)) < 0) goto done; - } - - if ((error = validate_existing( - make_path.ptr, &st, mode, flags, &opts->perfdata)) < 0) - goto done; } /* chmod if requested and necessary */ - if (((flags & GIT_MKDIR_CHMOD_PATH) != 0 || - (lastch == '\0' && (flags & GIT_MKDIR_CHMOD) != 0)) && - st.st_mode != mode) { - - opts->perfdata.chmod_calls++; - - if ((error = p_chmod(make_path.ptr, mode)) < 0 && - lastch == '\0') { - giterr_set(GITERR_OS, "Failed to set permissions on '%s'", - make_path.ptr); - goto done; - } - } + if ((error = mkdir_validate_mode( + make_path.ptr, &st, (lastch == '\0'), mode, flags, opts)) < 0) + goto done; if (opts->dir_map && opts->pool) { char *cache_path; From e164ddb11df0d7cd8178c759e323d18a7a7ea526 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 12:23:19 -0400 Subject: [PATCH 078/101] win32: return EACCES in `p_lstat` Don't coalesce all errors into ENOENT. At least identify EACCES. All callers should be handling this case already, as the POSIX `lstat` will return this. --- src/win32/posix_w32.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index c909af6cc04..414cb4701ec 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -148,12 +148,19 @@ static int lstat_w( return git_win32__file_attribute_to_stat(buf, &fdata, path); } - errno = ENOENT; + switch (GetLastError()) { + case ERROR_ACCESS_DENIED: + errno = EACCES; + break; + default: + errno = ENOENT; + break; + } /* To match POSIX behavior, set ENOTDIR when any of the folders in the * file path is a regular file, otherwise set ENOENT. */ - if (posix_enotdir) { + if (errno == ENOENT && posix_enotdir) { size_t path_len = wcslen(path); /* scan up path until we find an existing item */ From 9ce2e7b317277b1a27bbcef77fab4d053019b8d8 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 12:48:37 -0400 Subject: [PATCH 079/101] `mkdir`: cope with root path on win32 --- src/fileops.c | 7 ++++--- src/path.h | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 739a98fc404..57d2ce9c38e 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -408,7 +408,7 @@ int git_futils_mkdir( struct git_futils_mkdir_options opts = { 0 }; struct stat st; size_t depth = 0; - int len = 0, error; + int len = 0, root_len, error; if ((error = git_buf_puts(&make_path, path)) < 0 || (error = mkdir_canonicalize(&make_path, flags)) < 0 || @@ -416,6 +416,8 @@ int git_futils_mkdir( make_path.size == 0) goto done; + root_len = git_path_root(make_path.ptr); + /* find the first parent directory that exists. this will be used * as the base to dirname_relative. */ @@ -442,8 +444,7 @@ int git_futils_mkdir( /* we've walked all the given path's parents and it's either relative * or rooted. either way, give up and make the entire path. */ - if (len == 1 && - (parent_path.ptr[0] == '.' || parent_path.ptr[0] == '/')) { + if ((len == 1 && parent_path.ptr[0] == '.') || len == root_len+1) { relative = make_path.ptr; break; } diff --git a/src/path.h b/src/path.h index c76e90343e2..7e156fce825 100644 --- a/src/path.h +++ b/src/path.h @@ -72,7 +72,7 @@ extern const char *git_path_topdir(const char *path); * This will return a number >= 0 which is the offset to the start of the * path, if the path is rooted (i.e. "/rooted/path" returns 0 and * "c:/windows/rooted/path" returns 2). If the path is not rooted, this - * returns < 0. + * returns -1. */ extern int git_path_root(const char *path); From 5540d9db206c1c967f32d1e9f09f3568a275f3ae Mon Sep 17 00:00:00 2001 From: Dominique Leuenberger Date: Thu, 10 Sep 2015 16:11:10 +0200 Subject: [PATCH 080/101] pkg-config: fix directory references in libgit2.pc Before: libdir=/usr//usr/lib64 includedir=/usr//usr/include After: libdir=/usr/lib64 includedir=/usr/include (note the duplication of /usr in the before case) --- CMakeLists.txt | 17 +++++++++++++++++ libgit2.pc.in | 5 +++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 293153f8673..713640d6a7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,23 @@ SET(BIN_INSTALL_DIR bin CACHE PATH "Where to install binaries to.") SET(LIB_INSTALL_DIR lib CACHE PATH "Where to install libraries to.") SET(INCLUDE_INSTALL_DIR include CACHE PATH "Where to install headers to.") +# Set a couple variables to be substituted inside the .pc file. +# We can't just use LIB_INSTALL_DIR in the .pc file, as passing them as absolue +# or relative paths is both valid and supported by cmake. +SET (PKGCONFIG_PREFIX ${CMAKE_INSTALL_PREFIX}) + +IF(IS_ABSOLUTE ${LIB_INSTALL_DIR}) + SET (PKGCONFIG_LIBDIR ${LIB_INSTALL_DIR}) +ELSE(IS_ABSOLUTE ${LIB_INSTALL_DIR}) + SET (PKGCONFIG_LIBDIR "\${prefix}/${LIB_INSTALL_DIR}") +ENDIF (IS_ABSOLUTE ${LIB_INSTALL_DIR}) + +IF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + SET (PKGCONFIG_INCLUDEDIR ${INCLUDE_INSTALL_DIR}) +ELSE(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + SET (PKGCONFIG_INCLUDEDIR "\${prefix}/${INCLUDE_INSTALL_DIR}") +ENDIF(IS_ABSOLUTE ${INCLUDE_INSTALL_DIR}) + FUNCTION(TARGET_OS_LIBRARIES target) IF(WIN32) TARGET_LINK_LIBRARIES(${target} ws2_32) diff --git a/libgit2.pc.in b/libgit2.pc.in index 3d825a49fea..880266a302f 100644 --- a/libgit2.pc.in +++ b/libgit2.pc.in @@ -1,5 +1,6 @@ -libdir=@CMAKE_INSTALL_PREFIX@/@LIB_INSTALL_DIR@ -includedir=@CMAKE_INSTALL_PREFIX@/@INCLUDE_INSTALL_DIR@ +prefix=@PKGCONFIG_PREFIX@ +libdir=@PKGCONFIG_LIBDIR@ +includedir=@PKGCONFIG_INCLUDEDIR@ Name: libgit2 Description: The git library, take 2 From dfe2856d0f3eb66e9199d28a73fab71cad0f3ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 18 Sep 2015 12:06:55 +0200 Subject: [PATCH 081/101] Fix a couple of warnings --- tests/online/push.c | 1 - tests/submodule/lookup.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/online/push.c b/tests/online/push.c index efb763c2482..4d2b1d3109d 100644 --- a/tests/online/push.c +++ b/tests/online/push.c @@ -91,7 +91,6 @@ static int cred_acquire_cb( /** * git_push_status_foreach callback that records status entries. - * @param data (git_vector *) of push_status instances */ static int record_push_status_cb(const char *ref, const char *msg, void *payload) { diff --git a/tests/submodule/lookup.c b/tests/submodule/lookup.c index 5f161487176..38e0fa314f6 100644 --- a/tests/submodule/lookup.c +++ b/tests/submodule/lookup.c @@ -369,7 +369,7 @@ void test_submodule_lookup__renamed(void) /* Rename the entry in the index */ { const git_index_entry *e; - git_index_entry entry = { 0 }; + git_index_entry entry = {{ 0 }}; e = git_index_get_bypath(idx, "sm_unchanged", 0); cl_assert(e); From 08313c4b12ce80990018fa6fc9a7a93d0fb09ae0 Mon Sep 17 00:00:00 2001 From: Linquize Date: Fri, 18 Sep 2015 11:30:50 +0200 Subject: [PATCH 082/101] config: test that comments are left as with git --- tests/config/write.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/config/write.c b/tests/config/write.c index 9ad11ab273a..e634aa326cd 100644 --- a/tests/config/write.c +++ b/tests/config/write.c @@ -530,6 +530,9 @@ void test_config_write__outside_change(void) git_config_free(cfg); } +#define FOO_COMMENT \ + "; another comment!\n" + #define SECTION_FOO \ "\n" \ " \n" \ @@ -537,7 +540,8 @@ void test_config_write__outside_change(void) " # here's a comment\n" \ "\tname = \"value\"\n" \ " name2 = \"value2\"\n" \ - "; another comment!\n" + +#define SECTION_FOO_WITH_COMMENT SECTION_FOO FOO_COMMENT #define SECTION_BAR \ "[section \"bar\"]\t\n" \ @@ -553,7 +557,7 @@ void test_config_write__preserves_whitespace_and_comments(void) git_buf newfile = GIT_BUF_INIT; /* This config can occur after removing and re-adding the origin remote */ - const char *file_content = SECTION_FOO SECTION_BAR; + const char *file_content = SECTION_FOO_WITH_COMMENT SECTION_BAR; /* Write the test config and make sure the expected entry exists */ cl_git_mkfile(file_name, file_content); @@ -567,9 +571,10 @@ void test_config_write__preserves_whitespace_and_comments(void) cl_assert_equal_strn(SECTION_FOO, n, strlen(SECTION_FOO)); n += strlen(SECTION_FOO); - cl_assert_equal_strn("\tother = otherval\n", n, strlen("\tother = otherval\n")); n += strlen("\tother = otherval\n"); + cl_assert_equal_strn(FOO_COMMENT, n, strlen(FOO_COMMENT)); + n += strlen(FOO_COMMENT); cl_assert_equal_strn(SECTION_BAR, n, strlen(SECTION_BAR)); n += strlen(SECTION_BAR); From cd677b8fe0ff6d843d4733b1a08a5bcff89e4f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 18 Sep 2015 12:28:05 +0200 Subject: [PATCH 083/101] config: buffer comments to match git's variable-adding When there is a comment at the end of a section, git keeps it there, while we write the new variable right at the end. Keep comments buffered and dump them when we're going to output a variable or section, or reach EOF. This puts us in line with the config files which git produces. --- src/config_file.c | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/config_file.c b/src/config_file.c index a3fec1b349a..46f21c0f195 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -1711,6 +1711,7 @@ static const char *quotes_for_value(const char *value) struct write_data { git_buf *buf; + git_buf buffered_comment; unsigned int in_section : 1, preg_replaced : 1; const char *section; @@ -1719,16 +1720,21 @@ struct write_data { const char *value; }; -static int write_line(struct write_data *write_data, const char *line, size_t line_len) +static int write_line_to(git_buf *buf, const char *line, size_t line_len) { - int result = git_buf_put(write_data->buf, line, line_len); + int result = git_buf_put(buf, line, line_len); if (!result && line_len && line[line_len-1] != '\n') - result = git_buf_printf(write_data->buf, "\n"); + result = git_buf_printf(buf, "\n"); return result; } +static int write_line(struct write_data *write_data, const char *line, size_t line_len) +{ + return write_line_to(write_data->buf, line, line_len); +} + static int write_value(struct write_data *write_data) { const char *q; @@ -1770,6 +1776,14 @@ static int write_on_section( write_data->in_section = strcmp(current_section, write_data->section) == 0; + /* + * If there were comments just before this section, dump them as well. + */ + if (!result) { + result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size); + git_buf_clear(&write_data->buffered_comment); + } + if (!result) result = write_line(write_data, line, line_len); @@ -1787,10 +1801,19 @@ static int write_on_variable( { struct write_data *write_data = (struct write_data *)data; bool has_matched = false; + int error; GIT_UNUSED(reader); GIT_UNUSED(current_section); + /* + * If there were comments just before this variable, let's dump them as well. + */ + if ((error = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return error; + + git_buf_clear(&write_data->buffered_comment); + /* See if we are to update this name/value pair; first examine name */ if (write_data->in_section && strcasecmp(write_data->name, var_name) == 0) @@ -1825,7 +1848,7 @@ static int write_on_comment(struct reader **reader, const char *line, size_t lin GIT_UNUSED(reader); write_data = (struct write_data *)data; - return write_line(write_data, line, line_len); + return write_line_to(&write_data->buffered_comment, line, line_len); } static int write_on_eof(struct reader **reader, void *data) @@ -1835,6 +1858,12 @@ static int write_on_eof(struct reader **reader, void *data) GIT_UNUSED(reader); + /* + * If we've buffered comments when reaching EOF, make sure to dump them. + */ + if ((result = git_buf_put(write_data->buf, write_data->buffered_comment.ptr, write_data->buffered_comment.size)) < 0) + return result; + /* If we are at the EOF and have not written our value (again, for a * simple name/value set, not a multivar) then we have never seen the * section in question and should create a new section and write the @@ -1892,6 +1921,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p section = git__strndup(key, ldot - key); write_data.buf = &buf; + git_buf_init(&write_data.buffered_comment, 0); write_data.section = section; write_data.in_section = 0; write_data.preg_replaced = 0; @@ -1901,6 +1931,7 @@ static int config_write(diskfile_backend *cfg, const char *key, const regex_t *p result = config_parse(reader, write_on_section, write_on_variable, write_on_comment, write_on_eof, &write_data); git__free(section); + git_buf_free(&write_data.buffered_comment); if (result < 0) { git_filebuf_cleanup(&file); From e8ddd8d76c119903677b5d0c638c875023ae6784 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 17 Sep 2015 17:49:32 -0400 Subject: [PATCH 084/101] repo::reservedname: test a submodule update Test an initial submodule update, where we are trying to checkout the submodule for the first time, and placing a file within the submodule working directory with the same name as the submodule (and consequently, the same name as the repository itself). --- tests/repo/reservedname.c | 24 ++++++++++++++++++ tests/resources/sub.git/HEAD | 1 + tests/resources/sub.git/config | 8 ++++++ tests/resources/sub.git/index | Bin 0 -> 405 bytes tests/resources/sub.git/logs/HEAD | 1 + .../resources/sub.git/logs/refs/heads/master | 1 + .../10/ddd6d257e01349d514541981aeecea6b2e741d | Bin 0 -> 22 bytes .../17/6a458f94e0ea5272ce67c36bf30b6be9caf623 | Bin 0 -> 28 bytes .../94/c7d78d85c933d1d95b56bc2de01833ba8559fb | Bin 0 -> 132 bytes .../b7/a59b3f4ea13b985f8a1e0d3757d5cd3331add8 | Bin 0 -> 139 bytes .../d0/ee23c41b28746d7e822511d7838bce784ae773 | Bin 0 -> 54 bytes tests/resources/sub.git/refs/heads/master | 1 + tests/resources/super/.gitted/COMMIT_EDITMSG | 1 + tests/resources/super/.gitted/HEAD | 1 + tests/resources/super/.gitted/config | 10 ++++++++ tests/resources/super/.gitted/index | Bin 0 -> 217 bytes .../51/589c218bf77a8da9e9d8dbc097d76a742726c4 | Bin 0 -> 90 bytes .../79/d0d58ca6aa1688a073d280169908454cad5b91 | Bin 0 -> 132 bytes .../d7/57768b570a83e80d02edcc1032db14573e5034 | Bin 0 -> 87 bytes .../resources/super/.gitted/refs/heads/master | 1 + tests/resources/super/gitmodules | 3 +++ tests/submodule/submodule_helpers.c | 16 ++++++++++++ tests/submodule/submodule_helpers.h | 1 + 23 files changed, 69 insertions(+) create mode 100644 tests/resources/sub.git/HEAD create mode 100644 tests/resources/sub.git/config create mode 100644 tests/resources/sub.git/index create mode 100644 tests/resources/sub.git/logs/HEAD create mode 100644 tests/resources/sub.git/logs/refs/heads/master create mode 100644 tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d create mode 100644 tests/resources/sub.git/objects/17/6a458f94e0ea5272ce67c36bf30b6be9caf623 create mode 100644 tests/resources/sub.git/objects/94/c7d78d85c933d1d95b56bc2de01833ba8559fb create mode 100644 tests/resources/sub.git/objects/b7/a59b3f4ea13b985f8a1e0d3757d5cd3331add8 create mode 100644 tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 create mode 100644 tests/resources/sub.git/refs/heads/master create mode 100644 tests/resources/super/.gitted/COMMIT_EDITMSG create mode 100644 tests/resources/super/.gitted/HEAD create mode 100644 tests/resources/super/.gitted/config create mode 100644 tests/resources/super/.gitted/index create mode 100644 tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 create mode 100644 tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 create mode 100644 tests/resources/super/.gitted/objects/d7/57768b570a83e80d02edcc1032db14573e5034 create mode 100644 tests/resources/super/.gitted/refs/heads/master create mode 100644 tests/resources/super/gitmodules diff --git a/tests/repo/reservedname.c b/tests/repo/reservedname.c index faea0cc2b31..2a5b382395d 100644 --- a/tests/repo/reservedname.c +++ b/tests/repo/reservedname.c @@ -106,3 +106,27 @@ void test_repo_reservedname__submodule_pointer(void) git_repository_free(sub_repo); #endif } + +/* Like the `submodule_pointer` test (above), this ensures that we do not + * follow the gitlink to the submodule's repository location and treat that + * as a reserved name. This tests at an initial submodule update, where the + * submodule repo is being created. + */ +void test_repo_reservedname__submodule_pointer_during_create(void) +{ + git_repository *repo; + git_submodule *sm; + git_submodule_update_options update_options = GIT_SUBMODULE_UPDATE_OPTIONS_INIT; + git_buf url = GIT_BUF_INIT; + + repo = setup_fixture_super(); + + cl_git_pass(git_buf_joinpath(&url, clar_sandbox_path(), "sub.git")); + cl_repo_set_string(repo, "submodule.sub.url", url.ptr); + + cl_git_pass(git_submodule_lookup(&sm, repo, "sub")); + cl_git_pass(git_submodule_update(sm, 1, &update_options)); + + git_submodule_free(sm); + git_buf_free(&url); +} diff --git a/tests/resources/sub.git/HEAD b/tests/resources/sub.git/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests/resources/sub.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/resources/sub.git/config b/tests/resources/sub.git/config new file mode 100644 index 00000000000..78387c50b47 --- /dev/null +++ b/tests/resources/sub.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/tests/resources/sub.git/index b/tests/resources/sub.git/index new file mode 100644 index 0000000000000000000000000000000000000000..54be69e33e1917169be0a3fbaba5fd61746ea15a GIT binary patch literal 405 zcmZ?q402{*U|<4bmeAiS$AL5hkUj;X5ukAig3Z7yp5@v<<-x0SOTvLu;?`=J4eDP*<*dE;n62`k)BYy+6Fc>Nra9wz(d_-EKB)6_f kRq%Rq_qhtM=fz>Ee_l3S@LMBvbgC8SC8;G(PQUH}02WnsssI20 literal 0 HcmV?d00001 diff --git a/tests/resources/sub.git/logs/HEAD b/tests/resources/sub.git/logs/HEAD new file mode 100644 index 00000000000..f636268f6fd --- /dev/null +++ b/tests/resources/sub.git/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 b7a59b3f4ea13b985f8a1e0d3757d5cd3331add8 Edward Thomson 1442522322 -0400 commit (initial): Initial revision diff --git a/tests/resources/sub.git/logs/refs/heads/master b/tests/resources/sub.git/logs/refs/heads/master new file mode 100644 index 00000000000..f636268f6fd --- /dev/null +++ b/tests/resources/sub.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 b7a59b3f4ea13b985f8a1e0d3757d5cd3331add8 Edward Thomson 1442522322 -0400 commit (initial): Initial revision diff --git a/tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d b/tests/resources/sub.git/objects/10/ddd6d257e01349d514541981aeecea6b2e741d new file mode 100644 index 0000000000000000000000000000000000000000..a095b3fb822e3cde46d5d39ff21528c1e1fc70cf GIT binary patch literal 22 ecmb7F=sF|FfcPQQP4}zEJ-XWDauSLElDkA5YKY$pYq^UP|>;c z!`Yv?vtOS2rVLe?n3rFYky@lzQc=PnaQE7!@CU-4S4Bc38`r&gm91AI3sshunUjiB mjfnveC={0_F?TvpB(Aj# zP#zDX=M3Jai6%!5lQ)JGd5n0DNmG`}854s;^h*@sHCFC$qfh7rkCp4j4d%StA6;un toi|>_DRI4kvR0$kMr$}qE2Y@&J|6jxgt)gdN_axg@3Iwc;tNWyL3_OFJ+1%% literal 0 HcmV?d00001 diff --git a/tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 b/tests/resources/sub.git/objects/d0/ee23c41b28746d7e822511d7838bce784ae773 new file mode 100644 index 0000000000000000000000000000000000000000..d9bb9c84d8053600309e1bd6393d26b61c47bd78 GIT binary patch literal 54 zcmV-60LlM&0V^p=O;s>9XD~D{Ff%bxNY2R2Nzp5*C}9w|d+k#A17XjrA|aBE>)yP| M)+><(08@Jq$s_(2XaE2J literal 0 HcmV?d00001 diff --git a/tests/resources/sub.git/refs/heads/master b/tests/resources/sub.git/refs/heads/master new file mode 100644 index 00000000000..0e4d6e2a789 --- /dev/null +++ b/tests/resources/sub.git/refs/heads/master @@ -0,0 +1 @@ +b7a59b3f4ea13b985f8a1e0d3757d5cd3331add8 diff --git a/tests/resources/super/.gitted/COMMIT_EDITMSG b/tests/resources/super/.gitted/COMMIT_EDITMSG new file mode 100644 index 00000000000..e2d6b8987e3 --- /dev/null +++ b/tests/resources/super/.gitted/COMMIT_EDITMSG @@ -0,0 +1 @@ +submodule diff --git a/tests/resources/super/.gitted/HEAD b/tests/resources/super/.gitted/HEAD new file mode 100644 index 00000000000..cb089cd89a7 --- /dev/null +++ b/tests/resources/super/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests/resources/super/.gitted/config b/tests/resources/super/.gitted/config new file mode 100644 index 00000000000..06a8b77907e --- /dev/null +++ b/tests/resources/super/.gitted/config @@ -0,0 +1,10 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[submodule "sub"] + url = ../sub.git diff --git a/tests/resources/super/.gitted/index b/tests/resources/super/.gitted/index new file mode 100644 index 0000000000000000000000000000000000000000..cc2ffffb980f6eade02621e7431faffc830c96e9 GIT binary patch literal 217 zcmZ?q402{*U|<5_(BFmvK$-zYgV+$zxCF)m(!qfda}>M3SM{!ZdE@qh>DRML)YXnK zaOp0i)X@;dTKg{30SZ^EfSPb1kPO Nt$y+J!W`S|A^_*II9C7w literal 0 HcmV?d00001 diff --git a/tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 b/tests/resources/super/.gitted/objects/51/589c218bf77a8da9e9d8dbc097d76a742726c4 new file mode 100644 index 0000000000000000000000000000000000000000..727d3a696894fe8d07899ae0b520da02596720cd GIT binary patch literal 90 zcmV-g0HyzU0ZYosPg1ZjW{55>P0GzrDa}b$Py#ZQV!1dA5=$}^Y!!e!F3!@T93V5< wDkdg9vm_=aCo>618|fOy#FV5KmlVgu6r~pDmlh?b0+~P!dO%q&0A7+GCFlAeGXMYp literal 0 HcmV?d00001 diff --git a/tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 b/tests/resources/super/.gitted/objects/79/d0d58ca6aa1688a073d280169908454cad5b91 new file mode 100644 index 0000000000000000000000000000000000000000..7fd889d5f1cfe79f0b769d2682e3f5a888f0fa3b GIT binary patch literal 132 zcmV-~0DJ#<0i}&e3IZ_@06pgw{Q;%3nFxb;@dth&Nw>^^u^|h7-|-FJiaJyksdXEm zV?2z;3>16_=a_xK6fH+2G)$#vw1%>FK3Mi>ol0}8(%?>?)CeA{)GlvWc(*^g)vYw? m@*Jlk^$OZKAU@$Z=Ff%bx&`ZxO$<0qG%}Fh02#lDc*!{h#cje0)w+~Fe to>ii*cEr%k00 Date: Thu, 17 Sep 2015 18:12:05 -0400 Subject: [PATCH 085/101] repository: only reserve repo dirs in the workdir Check that the repository directory is beneath the workdir before adding it to the list of reserved paths. If it is not, then there is no possibility of checking out files into it, and it should not be a reserved word. This is a particular problem with submodules where the repo directory may be in the super's .git directory. --- src/repository.c | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/repository.c b/src/repository.c index 0f37cfbfe18..cbdfd582610 100644 --- a/src/repository.c +++ b/src/repository.c @@ -908,12 +908,28 @@ bool git_repository__reserved_names( buf->size = git_repository__reserved_names_win32[i].size; } - /* Try to add any repo-specific reserved names */ + /* Try to add any repo-specific reserved names - the gitlink file + * within a submodule or the repository (if the repository directory + * is beneath the workdir). These are typically `.git`, but should + * be protected in case they are not. Note, repo and workdir paths + * are always prettified to end in `/`, so a prefixcmp is safe. + */ if (!repo->is_bare) { - const char *reserved_path = repo->path_gitlink ? - repo->path_gitlink : repo->path_repository; + int (*prefixcmp)(const char *, const char *); + int error, ignorecase; - if (reserved_names_add8dot3(repo, reserved_path) < 0) + error = git_repository__cvar( + &ignorecase, repo, GIT_CVAR_IGNORECASE); + prefixcmp = (error || ignorecase) ? git__prefixcmp_icase : + git__prefixcmp; + + if (repo->path_gitlink && + reserved_names_add8dot3(repo, repo->path_gitlink) < 0) + goto on_error; + + if (repo->path_repository && + prefixcmp(repo->path_repository, repo->workdir) == 0 && + reserved_names_add8dot3(repo, repo->path_repository) < 0) goto on_error; } } From 92a47824d8d4dc62728ebb4ec1962ec03ae92d85 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 22 Sep 2015 23:10:56 -0400 Subject: [PATCH 086/101] win32: propogate filename too long errors --- src/win32/path_w32.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index 118e8bcc568..40b95c33ba1 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -198,13 +198,13 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) /* See if this is an absolute path (beginning with a drive letter) */ if (path__is_absolute(src)) { if (git__utf8_to_16(dest, MAX_PATH, src) < 0) - return -1; + goto on_error; } /* File-prefixed NT-style paths beginning with \\?\ */ else if (path__is_nt_namespace(src)) { /* Skip the NT prefix, the destination already contains it */ if (git__utf8_to_16(dest, MAX_PATH, src + PATH__NT_NAMESPACE_LEN) < 0) - return -1; + goto on_error; } /* UNC paths */ else if (path__is_unc(src)) { @@ -213,36 +213,43 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) /* Skip the leading "\\" */ if (git__utf8_to_16(dest, MAX_PATH - 2, src + 2) < 0) - return -1; + goto on_error; } /* Absolute paths omitting the drive letter */ else if (src[0] == '\\' || src[0] == '/') { if (path__cwd(dest, MAX_PATH) < 0) - return -1; + goto on_error; if (!path__is_absolute(dest)) { errno = ENOENT; - return -1; + goto on_error; } /* Skip the drive letter specification ("C:") */ if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0) - return -1; + goto on_error; } /* Relative paths */ else { int cwd_len; if ((cwd_len = git_win32_path__cwd(dest, MAX_PATH)) < 0) - return -1; + goto on_error; dest[cwd_len++] = L'\\'; if (git__utf8_to_16(dest + cwd_len, MAX_PATH - cwd_len, src) < 0) - return -1; + goto on_error; } return git_win32_path_canonicalize(out); + +on_error: + /* set windows error code so we can use its error message */ + if (errno == ENAMETOOLONG) + SetLastError(ERROR_FILENAME_EXCED_RANGE); + + return -1; } int git_win32_path_to_utf8(git_win32_utf8_path dest, const wchar_t *src) From 9768ebb1f385a1e26c317814951042089f6a65e9 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 22 Sep 2015 23:24:30 -0400 Subject: [PATCH 087/101] win32: test checkout msg on long path err --- tests/win32/longpath.c | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 tests/win32/longpath.c diff --git a/tests/win32/longpath.c b/tests/win32/longpath.c new file mode 100644 index 00000000000..4fe851c2ace --- /dev/null +++ b/tests/win32/longpath.c @@ -0,0 +1,62 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "buffer.h" +#include "fileops.h" + +static git_buf path = GIT_BUF_INIT; + +void test_win32_longpath__initialize(void) +{ +#ifdef GIT_WIN32 + const char *base = clar_sandbox_path(); + size_t base_len = strlen(base); + size_t remain = MAX_PATH - base_len; + size_t i; + + git_buf_clear(&path); + git_buf_puts(&path, base); + git_buf_putc(&path, '/'); + + cl_assert(remain < (MAX_PATH - 5)); + + for (i = 0; i < (remain - 5); i++) + git_buf_putc(&path, 'a'); + + printf("%s %" PRIuZ "\n", path.ptr, path.size); +#endif +} + +void test_win32_longpath__cleanup(void) +{ + git_buf_free(&path); +} + +#ifdef GIT_WIN32 +void assert_name_too_long(void) +{ + const git_error *err; + size_t expected_len, actual_len; + const char *expected_msg; + + err = giterr_last(); + actual_len = strlen(err->message); + + expected_msg = git_win32_get_error_message(ERROR_FILENAME_EXCED_RANGE); + expected_len = strlen(expected_msg); + + /* check the suffix */ + cl_assert_equal_s(expected_msg, err->message + (actual_len - expected_len)); +} +#endif + +void test_win32_longpath__errmsg_on_checkout(void) +{ +#ifdef GIT_WIN32 + git_repository *repo; + + cl_git_fail(git_clone(&repo, cl_fixture("testrepo.git"), path.ptr, NULL)); + assert_name_too_long(); +#endif +} From ab8f2c669a2a0bb66d5473f83ecb47ad805a7078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 23 Sep 2015 15:09:19 +0200 Subject: [PATCH 088/101] submodule: plug a few leaks --- src/submodule.c | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/submodule.c b/src/submodule.c index 1d73dc24e60..998ef91fd6a 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -170,7 +170,7 @@ static int name_from_path(git_buf *out, git_config *cfg, const char *path) git_buf_clear(out); git_buf_put(out, fdot + 1, ldot - fdot - 1); - return 0; + goto cleanup; } if (error == GIT_ITEROVER) { @@ -178,6 +178,8 @@ static int name_from_path(git_buf *out, git_config *cfg, const char *path) error = GIT_ENOTFOUND; } +cleanup: + git_config_iterator_free(iter); return error; } @@ -1701,7 +1703,7 @@ static int submodule_read_config(git_submodule *sm, git_config *cfg) GITERR_CHECK_ALLOC(sm->path); } } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "url")) == 0) { @@ -1709,7 +1711,7 @@ static int submodule_read_config(git_submodule *sm, git_config *cfg) sm->url = git__strdup(value); GITERR_CHECK_ALLOC(sm->url); } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "branch")) == 0) { @@ -1717,40 +1719,44 @@ static int submodule_read_config(git_submodule *sm, git_config *cfg) sm->branch = git__strdup(value); GITERR_CHECK_ALLOC(sm->branch); } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "update")) == 0) { in_config = 1; if ((error = git_submodule_parse_update(&sm->update, value)) < 0) - return error; + goto cleanup; sm->update_default = sm->update; } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "fetchRecurseSubmodules")) == 0) { in_config = 1; if ((error = git_submodule_parse_recurse(&sm->fetch_recurse, value)) < 0) - return error; + goto cleanup; sm->fetch_recurse_default = sm->fetch_recurse; } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if ((error = get_value(&value, cfg, &key, sm->name, "ignore")) == 0) { in_config = 1; if ((error = git_submodule_parse_ignore(&sm->ignore, value)) < 0) - return error; + goto cleanup; sm->ignore_default = sm->ignore; } else if (error != GIT_ENOTFOUND) { - return error; + goto cleanup; } if (in_config) sm->flags |= GIT_SUBMODULE_STATUS_IN_CONFIG; - return 0; + error = 0; + +cleanup: + git_buf_free(&key); + return error; } static int submodule_load_each(const git_config_entry *entry, void *payload) @@ -1784,8 +1790,10 @@ static int submodule_load_each(const git_config_entry *entry, void *payload) * already inserted, we've already loaded it, so we skip. */ pos = git_strmap_lookup_index(map, name.ptr); - if (git_strmap_valid_index(map, pos)) - return 0; + if (git_strmap_valid_index(map, pos)) { + error = 0; + goto done; + } if ((error = submodule_alloc(&sm, data->repo, name.ptr)) < 0) goto done; From 098f1e6e2506b3ca6376617751f877bee6fb5d20 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 24 Sep 2015 09:09:48 -0400 Subject: [PATCH 089/101] Use an array of forbidden custom headers --- src/transports/smart.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index 8388d9dc5bc..1ff39b48e0e 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,14 +66,20 @@ static int git_smart__set_callbacks( return 0; } -#define forbid_custom_header(disallowed_name) \ - if (strncmp(disallowed_name, custom_header, name_len) == 0) \ - return false +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; bool is_valid_custom_header(const char *custom_header) { const char *c; int name_len; + unsigned long i; if (custom_header == NULL) return true; @@ -95,12 +101,9 @@ bool is_valid_custom_header(const char *custom_header) return false; // Disallow headers that we set - forbid_custom_header("User-Agent"); - forbid_custom_header("Host"); - forbid_custom_header("Accept"); - forbid_custom_header("Content-Type"); - forbid_custom_header("Transfer-Encoding"); - forbid_custom_header("Content-Length"); + for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return false; return true; } From 63cc57232cebab60ad193731582e24e83ea5b3b9 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 24 Sep 2015 09:13:05 -0400 Subject: [PATCH 090/101] Don't null-check --- src/transports/smart.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index 1ff39b48e0e..fc7630c493f 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -81,9 +81,6 @@ bool is_valid_custom_header(const char *custom_header) int name_len; unsigned long i; - if (custom_header == NULL) - return true; - // Disallow \r and \n c = strchr(custom_header, '\r'); if (c != NULL) From e60db3c79a82459aff9bd90fc97ff9d5fb886a04 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 24 Sep 2015 09:24:10 -0400 Subject: [PATCH 091/101] Revise custom header error messages If the header doesn't look like a header (e.g. if it doesn't have a ":" or if it has newlines), report "custom HTTP header '%s' is malformed". If the header has the same name as a header already set by libgit2 (e.g. "Host"), report "HTTP header '%s' is already set by libgit2". --- src/transports/smart.c | 84 ++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index fc7630c493f..f0f212ca3e5 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,57 +66,55 @@ static int git_smart__set_callbacks( return 0; } -static char *forbidden_custom_headers[] = { - "User-Agent", - "Host", - "Accept", - "Content-Type", - "Transfer-Encoding", - "Content-Length", -}; +int http_header_name_length(const char *http_header) +{ + const char *colon = strchr(http_header, ':'); + if (!colon) + return 0; + return colon - http_header; +} -bool is_valid_custom_header(const char *custom_header) +bool is_malformed_http_header(const char *http_header) { const char *c; int name_len; - unsigned long i; // Disallow \r and \n - c = strchr(custom_header, '\r'); - if (c != NULL) - return false; - c = strchr(custom_header, '\n'); - if (c != NULL) - return false; + c = strchr(http_header, '\r'); + if (c) + return true; + c = strchr(http_header, '\n'); + if (c) + return true; // Require a header name followed by : - c = strchr(custom_header, ':'); - if (c == NULL) - return false; - name_len = c - custom_header; + name_len = http_header_name_length(http_header); if (name_len < 1) - return false; - - // Disallow headers that we set - for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) - if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) - return false; + return true; - return true; + return false; } -const char *find_invalid_custom_header(const git_strarray *custom_headers) -{ - size_t i; +static char *forbidden_custom_headers[] = { + "User-Agent", + "Host", + "Accept", + "Content-Type", + "Transfer-Encoding", + "Content-Length", +}; - if (custom_headers == NULL || custom_headers->count == 0) - return NULL; +bool is_forbidden_custom_header(const char *custom_header) +{ + unsigned long i; + int name_len = http_header_name_length(custom_header); - for (i = 0; i < custom_headers->count; i++) - if (!is_valid_custom_header(custom_headers->strings[i])) - return custom_headers->strings[i]; + // Disallow headers that we set + for (i = 0; i < ARRAY_SIZE(forbidden_custom_headers); i++) + if (strncmp(forbidden_custom_headers[i], custom_header, name_len) == 0) + return true; - return NULL; + return false; } static int git_smart__set_custom_headers( @@ -124,11 +122,17 @@ static int git_smart__set_custom_headers( const git_strarray *custom_headers) { transport_smart *t = (transport_smart *)transport; - const char *invalid_header = find_invalid_custom_header(custom_headers); + size_t i; - if (invalid_header != NULL) { - giterr_set(GITERR_INVALID, "Illegal HTTP header '%s'", invalid_header); - return -1; + for (i = 0; i < custom_headers->count; i++) { + if (is_malformed_http_header(custom_headers->strings[i])) { + giterr_set(GITERR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); + return -1; + } + if (is_forbidden_custom_header(custom_headers->strings[i])) { + giterr_set(GITERR_INVALID, "custom HTTP header '%s' is already set by libgit2", custom_headers->strings[i]); + return -1; + } } t->custom_headers = custom_headers; From d16c1b978fa3498a8a800d80ef6ed8a1e39ca314 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Thu, 24 Sep 2015 10:30:37 -0400 Subject: [PATCH 092/101] These can be static --- src/remote.c | 2 +- src/transports/smart.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/remote.c b/src/remote.c index ce6e13b3d39..84b8d590fe8 100644 --- a/src/remote.c +++ b/src/remote.c @@ -687,7 +687,7 @@ int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs) cbs->certificate_check, cbs->payload); } -int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) +static int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) { if (!t->set_custom_headers || !custom_headers) return 0; diff --git a/src/transports/smart.c b/src/transports/smart.c index f0f212ca3e5..8c5bc89e8bf 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -66,7 +66,7 @@ static int git_smart__set_callbacks( return 0; } -int http_header_name_length(const char *http_header) +static int http_header_name_length(const char *http_header) { const char *colon = strchr(http_header, ':'); if (!colon) @@ -74,7 +74,7 @@ int http_header_name_length(const char *http_header) return colon - http_header; } -bool is_malformed_http_header(const char *http_header) +static bool is_malformed_http_header(const char *http_header) { const char *c; int name_len; @@ -104,7 +104,7 @@ static char *forbidden_custom_headers[] = { "Content-Length", }; -bool is_forbidden_custom_header(const char *custom_header) +static bool is_forbidden_custom_header(const char *custom_header) { unsigned long i; int name_len = http_header_name_length(custom_header); From d7375662e7b4c885ea4865403e4db4c130e79198 Mon Sep 17 00:00:00 2001 From: Matt Burke Date: Fri, 25 Sep 2015 10:16:16 -0400 Subject: [PATCH 093/101] Copy custom_headers insteach of referencing the caller's copy --- src/remote.c | 2 +- src/transports/http.c | 8 +++----- src/transports/smart.c | 12 +++++++++--- src/transports/smart.h | 2 +- src/transports/winhttp.c | 26 ++++++++++++-------------- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/remote.c b/src/remote.c index 84b8d590fe8..2f8ffcb37b7 100644 --- a/src/remote.c +++ b/src/remote.c @@ -689,7 +689,7 @@ int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs) static int set_transport_custom_headers(git_transport *t, const git_strarray *custom_headers) { - if (!t->set_custom_headers || !custom_headers) + if (!t->set_custom_headers) return 0; return t->set_custom_headers(t, custom_headers); diff --git a/src/transports/http.c b/src/transports/http.c index 73ea0504300..e5f2b9f28b2 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -211,11 +211,9 @@ static int gen_request( } else git_buf_puts(buf, "Accept: */*\r\n"); - if (t->owner->custom_headers) { - for (i = 0; i < t->owner->custom_headers->count; i++) { - if (t->owner->custom_headers->strings[i]) - git_buf_printf(buf, "%s\r\n", t->owner->custom_headers->strings[i]); - } + for (i = 0; i < t->owner->custom_headers.count; i++) { + if (t->owner->custom_headers.strings[i]) + git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]); } /* Apply credentials to the request */ diff --git a/src/transports/smart.c b/src/transports/smart.c index 8c5bc89e8bf..b0611c35e69 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -124,6 +124,12 @@ static int git_smart__set_custom_headers( transport_smart *t = (transport_smart *)transport; size_t i; + if (t->custom_headers.count) + git_strarray_free(&t->custom_headers); + + if (!custom_headers) + return 0; + for (i = 0; i < custom_headers->count; i++) { if (is_malformed_http_header(custom_headers->strings[i])) { giterr_set(GITERR_INVALID, "custom HTTP header '%s' is malformed", custom_headers->strings[i]); @@ -135,9 +141,7 @@ static int git_smart__set_custom_headers( } } - t->custom_headers = custom_headers; - - return 0; + return git_strarray_copy(&t->custom_headers, custom_headers); } int git_smart__update_heads(transport_smart *t, git_vector *symrefs) @@ -436,6 +440,8 @@ static void git_smart__free(git_transport *transport) git_vector_free(refs); + git_strarray_free(&t->custom_headers); + git__free(t); } diff --git a/src/transports/smart.h b/src/transports/smart.h index 2c87e0200f3..800466adf81 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -139,7 +139,7 @@ typedef struct { git_transport_message_cb error_cb; git_transport_certificate_check_cb certificate_check_cb; void *message_cb_payload; - const git_strarray *custom_headers; + git_strarray custom_headers; git_smart_subtransport *wrapped; git_smart_subtransport_stream *current_stream; transport_smart_caps caps; diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index 5b00d9091fa..b364e906e94 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -410,21 +410,19 @@ static int winhttp_stream_connect(winhttp_stream *s) } } - if (t->owner->custom_headers) { - for (i = 0; i < t->owner->custom_headers->count; i++) { - if (t->owner->custom_headers->strings[i]) { - git_buf_clear(&buf); - git_buf_puts(&buf, t->owner->custom_headers->strings[i]); - if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { - giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); - goto on_error; - } + for (i = 0; i < t->owner->custom_headers.count; i++) { + if (t->owner->custom_headers.strings[i]) { + git_buf_clear(&buf); + git_buf_puts(&buf, t->owner->custom_headers.strings[i]); + if (git__utf8_to_16(ct, MAX_CONTENT_TYPE_LEN, git_buf_cstr(&buf)) < 0) { + giterr_set(GITERR_OS, "Failed to convert custom header to wide characters"); + goto on_error; + } - if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, - WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { - giterr_set(GITERR_OS, "Failed to add a header to the request"); - goto on_error; - } + if (!WinHttpAddRequestHeaders(s->request, ct, (ULONG)-1L, + WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE)) { + giterr_set(GITERR_OS, "Failed to add a header to the request"); + goto on_error; } } } From e4b2b919bb35d94d6dbcb5f7a31805788f2e335b Mon Sep 17 00:00:00 2001 From: Guille -bisho- Date: Fri, 25 Sep 2015 10:37:41 -0700 Subject: [PATCH 094/101] Fix binary diffs git expects an empty line after the binary data: literal X ...binary data... The last literal block of the generated patches were not containing the required empty line. Example: diff --git a/binary_file b/binary_file index 3f1b3f9098131cfecea4a50ff8afab349ea66d22..86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6 100644 GIT binary patch literal 8 Pc${NM&PdElPvrst3ey5{ literal 6 Nc${NM%g@i}0ssZ|0lokL diff --git a/binary_file2 b/binary_file2 index 31be99be19470da4af5b28b21e27896a2f2f9ee2..86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6 100644 GIT binary patch literal 8 Pc${NM&PdElPvrst3ey5{ literal 13 Sc${NMEKbZyOexL+Qd|HZV+4u- git apply of that diff results in: error: corrupt binary patch at line 9: diff --git a/binary_file2 b/binary_file2 fatal: patch with only garbage at line 10 The proper formating is: diff --git a/binary_file b/binary_file index 3f1b3f9098131cfecea4a50ff8afab349ea66d22..86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6 100644 GIT binary patch literal 8 Pc${NM&PdElPvrst3ey5{ literal 6 Nc${NM%g@i}0ssZ|0lokL diff --git a/binary_file2 b/binary_file2 index 31be99be19470da4af5b28b21e27896a2f2f9ee2..86e5c1008b5ce635d3e3fffa4434c5eccd8f00b6 100644 GIT binary patch literal 8 Pc${NM&PdElPvrst3ey5{ literal 13 Sc${NMEKbZyOexL+Qd|HZV+4u- --- src/diff_print.c | 2 +- tests/diff/binary.c | 21 ++++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index d406a441a52..bc2d6fab055 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -358,6 +358,7 @@ static int format_binary( scan += chunk_len; pi->line.num_lines++; } + git_buf_putc(pi->buf, '\n'); return 0; } @@ -416,7 +417,6 @@ static int diff_print_patch_file_binary( if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data, binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 || - (error = git_buf_putc(pi->buf, '\n')) < 0 || (error = format_binary(pi, binary->old_file.type, binary->old_file.data, binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) { diff --git a/tests/diff/binary.c b/tests/diff/binary.c index 5298e9ebbd6..173a5994e59 100644 --- a/tests/diff/binary.c +++ b/tests/diff/binary.c @@ -96,7 +96,8 @@ void test_diff_binary__add(void) "Kc${Nk-~s>u4FC%O\n" "\n" \ "literal 0\n" \ - "Hc$@u4FC%O\n"; + "Kc${Nk-~s>u4FC%O\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY; @@ -177,7 +179,8 @@ void test_diff_binary__delete(void) "Hc$@u4FC%O\n"; + "Kc${Nk-~s>u4FC%O\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -208,7 +211,8 @@ void test_diff_binary__delta(void) "delta 198\n" \ "zc$}LmI8{(0BqLQJI6p64AwNwaIJGP_Pr*5}Br~;mqJ$PQ;Y(X&QMK*C5^Br3bjG4d=XI^5@\n" \ - "JfH567LIF3FM2!Fd\n"; + "JfH567LIF3FM2!Fd\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -249,7 +253,8 @@ void test_diff_binary__delta_append(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -314,7 +319,8 @@ void test_diff_binary__index_to_workdir(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; @@ -379,7 +385,8 @@ void test_diff_binary__print_patch_from_diff(void) "nc%1vf+QYWt3zLL@hC)e3Vu?a>QDRl4f_G*?PG(-ZA}<#J$+QbW\n" \ "\n" \ "delta 7\n" \ - "Oc%18D`@*{63ljhg(E~C7\n"; + "Oc%18D`@*{63ljhg(E~C7\n" \ + "\n"; opts.flags = GIT_DIFF_SHOW_BINARY | GIT_DIFF_FORCE_BINARY; opts.id_abbrev = GIT_OID_HEXSZ; From 53a2870514fb06b7aebfd1c55c19a61704906596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 27 Sep 2015 22:48:39 +0200 Subject: [PATCH 095/101] net: add tests against badssl.com These provide bad X.509 certificates, which we should refuse to connect to by default. --- tests/online/badssl.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/online/badssl.c diff --git a/tests/online/badssl.c b/tests/online/badssl.c new file mode 100644 index 00000000000..850468320e9 --- /dev/null +++ b/tests/online/badssl.c @@ -0,0 +1,27 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" + +static git_repository *g_repo; + +#if defined(GIT_OPENSSL) || defined(GIT_WINHTTP) || defined(GIT_SECURE_TRANSPORT) + +void test_online_badssl__expired(void) +{ + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL)); +} + +void test_online_badssl__wrong_host(void) +{ + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL)); +} + +void test_online_badssl__self_signed(void) +{ + cl_git_fail_with(GIT_ECERTIFICATE, + git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL)); +} + +#endif From 5c5df666b0b2ed4433c6fb931280f9641e967a13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 27 Sep 2015 23:32:20 +0200 Subject: [PATCH 096/101] Plug some leaks --- src/submodule.c | 3 +++ tests/core/mkdir.c | 2 ++ tests/index/rename.c | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/src/submodule.c b/src/submodule.c index 998ef91fd6a..3fd3388439c 100644 --- a/src/submodule.c +++ b/src/submodule.c @@ -229,6 +229,7 @@ int git_submodule_lookup( if (error < 0) { git_submodule_free(sm); + git_buf_free(&path); return error; } @@ -1699,6 +1700,8 @@ static int submodule_read_config(git_submodule *sm, git_config *cfg) * should be strcasecmp */ if (strcmp(sm->name, value) != 0) { + if (sm->path != sm->name) + git__free(sm->path); sm->path = git__strdup(value); GITERR_CHECK_ALLOC(sm->path); } diff --git a/tests/core/mkdir.c b/tests/core/mkdir.c index 5e6a060021a..96c9723962b 100644 --- a/tests/core/mkdir.c +++ b/tests/core/mkdir.c @@ -48,6 +48,8 @@ void test_core_mkdir__absolute(void) cl_assert(!git_path_isdir(path.ptr)); cl_git_fail(git_futils_mkdir(path.ptr, 0755, 0)); cl_assert(!git_path_isdir(path.ptr)); + + git_buf_free(&path); } void test_core_mkdir__basic(void) diff --git a/tests/index/rename.c b/tests/index/rename.c index ebaa9b7404a..86eaf0053f3 100644 --- a/tests/index/rename.c +++ b/tests/index/rename.c @@ -77,5 +77,10 @@ void test_index_rename__casechanging(void) cl_assert_equal_i(1, git_index_entrycount(index)); else cl_assert_equal_i(2, git_index_entrycount(index)); + + git_index_free(index); + git_repository_free(repo); + + cl_fixture_cleanup("rename"); } From ea467e74871830da77bec3e351172a637c139823 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 28 Sep 2015 16:46:09 -0400 Subject: [PATCH 097/101] win32::longpath: don't print path --- tests/win32/longpath.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/win32/longpath.c b/tests/win32/longpath.c index 4fe851c2ace..6de7d389a78 100644 --- a/tests/win32/longpath.c +++ b/tests/win32/longpath.c @@ -23,8 +23,6 @@ void test_win32_longpath__initialize(void) for (i = 0; i < (remain - 5); i++) git_buf_putc(&path, 'a'); - - printf("%s %" PRIuZ "\n", path.ptr, path.size); #endif } From 146a96de82aebeca5e9b5bfe7fc69456f2bf2d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Sep 2015 09:41:25 +0200 Subject: [PATCH 098/101] openssl: don't try to teardown an unconnected SSL context SSL_shutdown() does not like it when we pass an unitialized ssl context to it. This means that when we fail to connect to a host, we hide the error message saying so with OpenSSL's indecipherable error message. --- src/openssl_stream.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/openssl_stream.c b/src/openssl_stream.c index 8ff53d4b146..54dd761ca11 100644 --- a/src/openssl_stream.c +++ b/src/openssl_stream.c @@ -302,6 +302,7 @@ static int verify_server_cert(SSL *ssl, const char *host) typedef struct { git_stream parent; git_stream *io; + bool connected; char *host; SSL *ssl; git_cert_x509 cert_info; @@ -318,6 +319,8 @@ int openssl_connect(git_stream *stream) if ((ret = git_stream_connect(st->io)) < 0) return ret; + st->connected = true; + bio = BIO_new(&git_stream_bio_method); GITERR_CHECK_ALLOC(bio); bio->ptr = st->io; @@ -406,9 +409,11 @@ int openssl_close(git_stream *stream) openssl_stream *st = (openssl_stream *) stream; int ret; - if ((ret = ssl_teardown(st->ssl)) < 0) + if (st->connected && (ret = ssl_teardown(st->ssl)) < 0) return -1; + st->connected = false; + return git_stream_close(st->io); } From 8649dfd8df4f0d840a64c1d6c5fc80b8e94a68d1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 29 Sep 2015 13:36:37 -0400 Subject: [PATCH 099/101] p_futimes: support using futimens when available --- CMakeLists.txt | 6 ++++++ src/unix/posix.h | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c03c718c7e..a0ef89f760c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,6 +19,7 @@ CMAKE_POLICY(SET CMP0015 NEW) SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/") INCLUDE(CheckLibraryExists) +INCLUDE(CheckFunctionExists) INCLUDE(AddCFlagIfSupported) INCLUDE(FindPkgConfig) @@ -431,6 +432,11 @@ ELSE () ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-const-variable) ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-function) + CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) + IF (HAVE_FUTIMENS) + ADD_DEFINITIONS(-DHAVE_FUTIMENS) + ENDIF () + IF (APPLE) # Apple deprecated OpenSSL ADD_C_FLAG_IF_SUPPORTED(-Wno-deprecated-declarations) ENDIF() diff --git a/src/unix/posix.h b/src/unix/posix.h index 7773509907f..6633689bcfc 100644 --- a/src/unix/posix.h +++ b/src/unix/posix.h @@ -22,7 +22,6 @@ typedef int GIT_SOCKET; #define p_stat(p,b) stat(p, b) #define p_utimes(f, t) utimes(f, t) -#define p_futimes(f, t) futimes(f, t) #define p_readlink(a, b, c) readlink(a, b, c) #define p_symlink(o,n) symlink(o, n) @@ -53,4 +52,18 @@ extern char *p_realpath(const char *, char *); #define p_localtime_r(c, r) localtime_r(c, r) #define p_gmtime_r(c, r) gmtime_r(c, r) +#ifdef HAVE_FUTIMENS +GIT_INLINE(int) p_futimes(int f, const struct timeval t[2]) +{ + struct timespec s[2]; + s[0].tv_sec = t[0].tv_sec; + s[0].tv_nsec = t[0].tv_usec * 1000; + s[1].tv_sec = t[1].tv_sec; + s[1].tv_nsec = t[1].tv_usec * 1000; + return futimens(f, s); +} +#else +# define p_futimes futimes +#endif + #endif From e683d15247ef7231143b46580f07113ecae43773 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 30 Sep 2015 05:49:04 -0400 Subject: [PATCH 100/101] qsort_r/qsort_s: detect their support --- CMakeLists.txt | 20 +++++++++++++++----- src/util.c | 21 ++++++++------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0ef89f760c..e6c06413b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -432,11 +432,6 @@ ELSE () ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-const-variable) ADD_C_FLAG_IF_SUPPORTED(-Wno-unused-function) - CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) - IF (HAVE_FUTIMENS) - ADD_DEFINITIONS(-DHAVE_FUTIMENS) - ENDIF () - IF (APPLE) # Apple deprecated OpenSSL ADD_C_FLAG_IF_SUPPORTED(-Wno-deprecated-declarations) ENDIF() @@ -447,6 +442,21 @@ ELSE () ENDIF () ENDIF() +CHECK_FUNCTION_EXISTS(futimens HAVE_FUTIMENS) +IF (HAVE_FUTIMENS) + ADD_DEFINITIONS(-DHAVE_FUTIMENS) +ENDIF () + +CHECK_FUNCTION_EXISTS(qsort_r HAVE_QSORT_R) +IF (HAVE_QSORT_R) + ADD_DEFINITIONS(-DHAVE_QSORT_R) +ENDIF () + +CHECK_FUNCTION_EXISTS(qsort_s HAVE_QSORT_S) +IF (HAVE_QSORT_S) + ADD_DEFINITIONS(-DHAVE_QSORT_S) +ENDIF () + IF( NOT CMAKE_CONFIGURATION_TYPES ) # Build Debug by default IF (NOT CMAKE_BUILD_TYPE) diff --git a/src/util.c b/src/util.c index b3929bca232..9e67f434743 100644 --- a/src/util.c +++ b/src/util.c @@ -611,7 +611,7 @@ size_t git__unescape(char *str) return (pos - str); } -#if defined(GIT_WIN32) || defined(BSD) +#if defined(HAVE_QSORT_S) || (defined(HAVE_QSORT_R) && defined(BSD)) typedef struct { git__sort_r_cmp cmp; void *payload; @@ -628,21 +628,16 @@ static int GIT_STDLIB_CALL git__qsort_r_glue_cmp( void git__qsort_r( void *els, size_t nel, size_t elsize, git__sort_r_cmp cmp, void *payload) { -#if defined(__MINGW32__) || defined(AMIGA) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || \ - defined(__gnu_hurd__) || defined(__ANDROID_API__) || \ - defined(__sun) || defined(__CYGWIN__) || \ - (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8) || \ - (defined(_MSC_VER) && _MSC_VER < 1500) - git__insertsort_r(els, nel, elsize, NULL, cmp, payload); -#elif defined(GIT_WIN32) - git__qsort_r_glue glue = { cmp, payload }; - qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); -#elif defined(BSD) +#if defined(HAVE_QSORT_R) && defined(BSD) git__qsort_r_glue glue = { cmp, payload }; qsort_r(els, nel, elsize, &glue, git__qsort_r_glue_cmp); -#else +#elif defined(HAVE_QSORT_R) && defined(__GLIBC__) qsort_r(els, nel, elsize, cmp, payload); +#elif defined(HAVE_QSORT_S) + git__qsort_r_glue glue = { cmp, payload }; + qsort_s(els, nel, elsize, git__qsort_r_glue_cmp, &glue); +#else + git__insertsort_r(els, nel, elsize, NULL, cmp, payload); #endif } From d3b29fb94bf1c1d0caec39b4a2c3d2061c63efec Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 1 Oct 2015 00:50:37 +0200 Subject: [PATCH 101/101] refdb and odb backends must provide `free` function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As refdb and odb backends can be allocated by client code, libgit2 can’t know whether an alternative memory allocator was used, and thus should not try to call `git__free` on those objects. Instead, odb and refdb backend implementations must always provide their own `free` functions to ensure memory gets freed correctly. --- include/git2/sys/odb_backend.h | 4 ++++ include/git2/sys/refdb_backend.h | 4 ++-- src/odb.c | 3 +-- src/refdb.c | 8 ++------ tests/odb/sorting.c | 1 + 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/git2/sys/odb_backend.h b/include/git2/sys/odb_backend.h index fe102ff3cab..e423a92360a 100644 --- a/include/git2/sys/odb_backend.h +++ b/include/git2/sys/odb_backend.h @@ -83,6 +83,10 @@ struct git_odb_backend { git_odb_writepack **, git_odb_backend *, git_odb *odb, git_transfer_progress_cb progress_cb, void *progress_payload); + /** + * Frees any resources held by the odb (including the `git_odb_backend` + * itself). An odb backend implementation must provide this function. + */ void (* free)(git_odb_backend *); }; diff --git a/include/git2/sys/refdb_backend.h b/include/git2/sys/refdb_backend.h index 01fce80096e..5129ad84a78 100644 --- a/include/git2/sys/refdb_backend.h +++ b/include/git2/sys/refdb_backend.h @@ -130,8 +130,8 @@ struct git_refdb_backend { int (*ensure_log)(git_refdb_backend *backend, const char *refname); /** - * Frees any resources held by the refdb. A refdb implementation may - * provide this function; if it is not provided, nothing will be done. + * Frees any resources held by the refdb (including the `git_refdb_backend` + * itself). A refdb backend implementation must provide this function. */ void (*free)(git_refdb_backend *backend); diff --git a/src/odb.c b/src/odb.c index b2d635109cb..2b2c35fe88e 100644 --- a/src/odb.c +++ b/src/odb.c @@ -600,8 +600,7 @@ static void odb_free(git_odb *db) backend_internal *internal = git_vector_get(&db->backends, i); git_odb_backend *backend = internal->backend; - if (backend->free) backend->free(backend); - else git__free(backend); + backend->free(backend); git__free(internal); } diff --git a/src/refdb.c b/src/refdb.c index 16fb519a698..debba1276a5 100644 --- a/src/refdb.c +++ b/src/refdb.c @@ -61,12 +61,8 @@ int git_refdb_open(git_refdb **out, git_repository *repo) static void refdb_free_backend(git_refdb *db) { - if (db->backend) { - if (db->backend->free) - db->backend->free(db->backend); - else - git__free(db->backend); - } + if (db->backend) + db->backend->free(db->backend); } int git_refdb_set_backend(git_refdb *db, git_refdb_backend *backend) diff --git a/tests/odb/sorting.c b/tests/odb/sorting.c index 147a160c873..d24c49c6937 100644 --- a/tests/odb/sorting.c +++ b/tests/odb/sorting.c @@ -14,6 +14,7 @@ static git_odb_backend *new_backend(size_t position) if (b == NULL) return NULL; + b->base.free = (void (*)(git_odb_backend *)) git__free; b->base.version = GIT_ODB_BACKEND_VERSION; b->position = position; return (git_odb_backend *)b;