From 48ebea662a33a3b918143c014dde88e58e6d0a75 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 7 Jan 2013 00:20:13 +0100 Subject: [PATCH 01/95] tests: Introduce count_config_entries_match() helper --- tests/config/config_helpers.c | 28 ++++++++++++++++++++++++++++ tests/config/config_helpers.h | 4 ++++ 2 files changed, 32 insertions(+) diff --git a/tests/config/config_helpers.c b/tests/config/config_helpers.c index 53bd945a0..35da720e0 100644 --- a/tests/config/config_helpers.c +++ b/tests/config/config_helpers.c @@ -35,3 +35,31 @@ void assert_config_entry_value( cl_assert_equal_s(expected_value, out); } + +static int count_config_entries_cb( + const git_config_entry *entry, + void *payload) +{ + int *how_many = (int *)payload; + + GIT_UNUSED(entry); + + (*how_many)++; + + return 0; +} + +int count_config_entries_match(git_repository *repo, const char *pattern) +{ + git_config *config; + int how_many = 0; + + cl_git_pass(git_repository_config(&config, repo)); + + cl_assert_equal_i(0, git_config_foreach_match( + config, pattern, count_config_entries_cb, &how_many)); + + git_config_free(config); + + return how_many; +} diff --git a/tests/config/config_helpers.h b/tests/config/config_helpers.h index b887b3d38..440645730 100644 --- a/tests/config/config_helpers.h +++ b/tests/config/config_helpers.h @@ -7,3 +7,7 @@ extern void assert_config_entry_value( git_repository *repo, const char *name, const char *expected_value); + +extern int count_config_entries_match( + git_repository *repo, + const char *pattern); From 40e48ea40f5dfe0fbf786efc89d4cf297f4525e1 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 15 Nov 2013 15:36:37 +0000 Subject: [PATCH 02/95] remote: Introduce git_remote_delete() --- include/git2/remote.h | 13 ++++ src/remote.c | 117 +++++++++++++++++++++++++++++++++- tests/network/remote/delete.c | 57 +++++++++++++++++ 3 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 tests/network/remote/delete.c diff --git a/include/git2/remote.h b/include/git2/remote.h index 11e1e26d0..62608358d 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -611,6 +611,19 @@ GIT_EXTERN(void) git_remote_set_update_fetchhead(git_remote *remote, int value); */ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); +/** +* Delete an existing persisted remote. +* +* All remote-tracking branches and configuration settings +* for the remote will be removed. +* +* once deleted, the passed remote object will be freed and invalidated. +* +* @param remote A valid remote +* @return 0 on success, or an error code. +*/ +GIT_EXTERN(int) git_remote_delete(git_remote *remote); + /** @} */ GIT_END_DECL #endif diff --git a/src/remote.c b/src/remote.c index ea638e373..8bd52e7f2 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1303,13 +1303,14 @@ static int rename_remote_config_section( if (git_buf_printf(&old_section_name, "remote.%s", old_name) < 0) goto cleanup; - if (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0) - goto cleanup; + if (new_name && + (git_buf_printf(&new_section_name, "remote.%s", new_name) < 0)) + goto cleanup; error = git_config_rename_section( repo, git_buf_cstr(&old_section_name), - git_buf_cstr(&new_section_name)); + new_name ? git_buf_cstr(&new_section_name) : NULL); cleanup: git_buf_free(&old_section_name); @@ -1747,3 +1748,113 @@ int git_remote_init_callbacks(git_remote_callbacks* opts, int version) return 0; } } + +struct branch_removal_data { + git_vector branches; + const char *name; +}; + +static int retrieve_branches_cb( + const git_config_entry *entry, + void *payload) +{ + int error; + struct branch_removal_data *data = (struct branch_removal_data *)payload; + + if (strcmp(data->name, entry->value)) + return 0; + + error = git_vector_insert( + &data->branches, + git__strndup( + entry->name + strlen("branch."), + strlen(entry->name) - strlen("branch.") - strlen(".remote"))); + + return error; +} + +static int delete_branch_remote_config_entry( + git_config *config, + const char *branch_name) +{ + int error; + + git_buf config_entry = GIT_BUF_INIT; + + if (git_buf_printf(&config_entry, "branch.%s.%s", branch_name, "remote") < 0) + return -1; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&config_entry))) < 0) + goto cleanup; + + git_buf_clear(&config_entry); + + if (git_buf_printf(&config_entry, "branch.%s.%s", branch_name, "merge") < 0) + return -1; + + error = git_config_delete_entry(config, git_buf_cstr(&config_entry)); + +cleanup: + git_buf_free(&config_entry); + + return error; +} + +static int remove_branch_config_related_entries( + git_repository *repo, + const char *remote_name) +{ + int error; + git_config *config; + size_t i; + char *branch_name; + struct branch_removal_data data; + + if ((error = git_repository_config__weakptr(&config, repo)) < 0) + return error; + + if ((error = git_vector_init(&data.branches, 4, git__strcmp_cb)) < 0) + return error; + + data.name = remote_name; + + error = git_config_foreach_match( + config, "branch\\..+\\.remote", retrieve_branches_cb, &data); + + git_vector_foreach(&data.branches, i, branch_name) { + if (!error) + error = delete_branch_remote_config_entry(config, branch_name); + + git__free(branch_name); + } + + git_vector_free(&data.branches); + return error; +} + +int git_remote_delete(git_remote *remote) +{ + int error; + git_repository *repo; + + assert(remote); + + if (!remote->name) { + giterr_set(GITERR_INVALID, "Can't delete an anonymous remote."); + return -1; + } + + repo = git_remote_owner(remote); + + if ((error = rename_remote_config_section( + repo, git_remote_name(remote), NULL)) < 0) + return error; + + if ((error = remove_branch_config_related_entries(repo, + git_remote_name(remote))) < 0) + return error; + + git_remote_free(remote); + + return 0; +} diff --git a/tests/network/remote/delete.c b/tests/network/remote/delete.c new file mode 100644 index 000000000..5bf944cda --- /dev/null +++ b/tests/network/remote/delete.c @@ -0,0 +1,57 @@ +#include "clar_libgit2.h" +#include "config/config_helpers.h" + +#include "repository.h" + +static git_remote *_remote; +static git_repository *_repo; + +void test_network_remote_delete__initialize(void) +{ + _repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_remote_load(&_remote, _repo, "test")); +} + +void test_network_remote_delete__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + +void test_network_remote_delete__cannot_delete_an_anonymous_remote(void) +{ + git_remote *remote; + + cl_git_pass(git_remote_create_anonymous(&remote, _repo, "git://github.com/libgit2/libgit2", NULL)); + + cl_git_fail(git_remote_delete(remote)); + + git_remote_free(remote); +} + +void test_network_remote_delete__deleting_a_remote_removes_the_remote_tracking_references(void) +{ + cl_assert(false); +} + +void test_network_remote_delete__deleting_a_remote_removes_the_remote_configuration_settings(void) +{ + cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0); + + cl_git_pass(git_remote_delete(_remote)); + + cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+")); +} + +void test_network_remote_delete__deleting_a_remote_removes_the_branch_remote_configuration_settings(void) +{ + assert_config_entry_existence(_repo, "branch.mergeless.remote", true); + assert_config_entry_existence(_repo, "branch.master.remote", true); + + cl_git_pass(git_remote_delete(_remote)); + + assert_config_entry_existence(_repo, "branch.mergeless.remote", false); + assert_config_entry_existence(_repo, "branch.mergeless.merge", false); + assert_config_entry_existence(_repo, "branch.master.remote", false); + assert_config_entry_existence(_repo, "branch.master.merge", false); +} From 5cdac19caa6c0bf81c5a71d3801be73281387cd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Apr 2014 08:29:14 +0200 Subject: [PATCH 03/95] remote: move branch upstream deletion to use an iterator This should make it more readable and allocate a bunch fewer strings. --- src/remote.c | 95 +++++++++++++++++++++------------------------------- 1 file changed, 38 insertions(+), 57 deletions(-) diff --git a/src/remote.c b/src/remote.c index 8bd52e7f2..ca1099a7f 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1749,55 +1749,19 @@ int git_remote_init_callbacks(git_remote_callbacks* opts, int version) } } -struct branch_removal_data { - git_vector branches; - const char *name; -}; - -static int retrieve_branches_cb( - const git_config_entry *entry, - void *payload) +/* asserts a branch..remote format */ +static const char *name_offset(size_t *len_out, const char *name) { - int error; - struct branch_removal_data *data = (struct branch_removal_data *)payload; + size_t prefix_len; + const char *dot; - if (strcmp(data->name, entry->value)) - return 0; + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); - error = git_vector_insert( - &data->branches, - git__strndup( - entry->name + strlen("branch."), - strlen(entry->name) - strlen("branch.") - strlen(".remote"))); + assert(dot); - return error; -} - -static int delete_branch_remote_config_entry( - git_config *config, - const char *branch_name) -{ - int error; - - git_buf config_entry = GIT_BUF_INIT; - - if (git_buf_printf(&config_entry, "branch.%s.%s", branch_name, "remote") < 0) - return -1; - - if ((error = git_config_delete_entry(config, git_buf_cstr(&config_entry))) < 0) - goto cleanup; - - git_buf_clear(&config_entry); - - if (git_buf_printf(&config_entry, "branch.%s.%s", branch_name, "merge") < 0) - return -1; - - error = git_config_delete_entry(config, git_buf_cstr(&config_entry)); - -cleanup: - git_buf_free(&config_entry); - - return error; + *len_out = dot - name - prefix_len; + return name + prefix_len; } static int remove_branch_config_related_entries( @@ -1806,29 +1770,46 @@ static int remove_branch_config_related_entries( { int error; git_config *config; - size_t i; - char *branch_name; - struct branch_removal_data data; + git_config_entry *entry; + git_config_iterator *iter; + git_buf buf = GIT_BUF_INIT; if ((error = git_repository_config__weakptr(&config, repo)) < 0) return error; - if ((error = git_vector_init(&data.branches, 4, git__strcmp_cb)) < 0) + if ((error = git_config_iterator_glob_new(&iter, config, "branch\\..+\\.remote")) < 0) return error; - data.name = remote_name; + /* find any branches with us as upstream and remove that config */ + while ((error = git_config_next(&entry, iter)) == 0) { + const char *branch; + size_t branch_len; - error = git_config_foreach_match( - config, "branch\\..+\\.remote", retrieve_branches_cb, &data); + if (strcmp(remote_name, entry->value)) + continue; - git_vector_foreach(&data.branches, i, branch_name) { - if (!error) - error = delete_branch_remote_config_entry(config, branch_name); + branch = name_offset(&branch_len, entry->name); - git__free(branch_name); + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.merge", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; + + git_buf_clear(&buf); + if (git_buf_printf(&buf, "branch.%.*s.remote", (int)branch_len, branch) < 0) + break; + + if ((error = git_config_delete_entry(config, git_buf_cstr(&buf))) < 0) + break; } - git_vector_free(&data.branches); + if (error == GIT_ITEROVER) + error = 0; + + git_buf_free(&buf); + git_config_iterator_free(iter); return error; } From 1017f81f0049f493508c1409418af725e8d6998f Mon Sep 17 00:00:00 2001 From: Jacques Germishuys Date: Sun, 27 Apr 2014 13:44:06 +0200 Subject: [PATCH 04/95] Undef lseek first --- src/win32/mingw-compat.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/win32/mingw-compat.h b/src/win32/mingw-compat.h index 8f51d6f5a..059e39cbc 100644 --- a/src/win32/mingw-compat.h +++ b/src/win32/mingw-compat.h @@ -10,6 +10,7 @@ #if defined(__MINGW32__) /* use a 64-bit file offset type */ +# undef lseek # define lseek _lseeki64 # undef stat # define stat _stati64 From 6e94a1efbca605957d6fc56ae068f30ef57c3c01 Mon Sep 17 00:00:00 2001 From: Jacques Germishuys Date: Sun, 27 Apr 2014 14:25:49 +0200 Subject: [PATCH 05/95] _InterlockedExchange expects a volatile LONG --- src/global.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/global.c b/src/global.c index 15baf1eb8..2ed5b4c6d 100644 --- a/src/global.c +++ b/src/global.c @@ -74,7 +74,7 @@ static void git__shutdown(void) #if defined(GIT_THREADS) && defined(GIT_WIN32) static DWORD _tls_index; -static DWORD _mutex = 0; +static volatile LONG _mutex = 0; static int synchronized_threads_init() { From f5dd2a289106c74647d35560eee55a48ff0f123f Mon Sep 17 00:00:00 2001 From: Jacques Germishuys Date: Sun, 27 Apr 2014 15:00:00 +0200 Subject: [PATCH 06/95] git_pool_mallocsz takes an unsigned long --- src/attrcache.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/attrcache.c b/src/attrcache.c index f1bc70467..ec22eab25 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -53,7 +53,7 @@ int git_attr_cache__alloc_file_entry( cachesize++; } - ce = git_pool_mallocz(pool, cachesize); + ce = git_pool_mallocz(pool, (uint32_t)cachesize); GITERR_CHECK_ALLOC(ce); if (baselen) { From f554611a274aafd702bd899f4663c16eb76ae8f0 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 6 May 2014 12:41:26 -0700 Subject: [PATCH 07/95] Improve checks for ignore containment The diff code was using an "ignored_prefix" directory to track if a parent directory was ignored that contained untracked files alongside tracked files. Unfortunately, when negative ignore rules were used for directories inside ignored parents, the wrong rules were applied to untracked files inside the negatively ignored child directories. This commit moves the logic for ignore containment into the workdir iterator (which is a better place for it), so the ignored-ness of a directory is contained in the frame stack during traversal. This allows a child directory to override with a negative ignore and yet still restore the ignored state of the parent when we traverse out of the child. Along with this, there are some problems with "directory only" ignore rules on container directories. Given "a/*" and "!a/b/c/" (where the second rule is a directory rule but the first rule is just a generic prefix rule), then the directory only constraint was having "a/b/c/d/file" match the first rule and not the second. This was fixed by having ignore directory-only rules test a rule against the prefix of a file with LEADINGDIR enabled. Lastly, spot checks for ignores using `git_ignore_path_is_ignored` were tested from the top directory down to the bottom to deal with the containment problem, but this is wrong. We have to test bottom to top so that negative subdirectory rules will be checked before parent ignore rules. This does change the behavior of some existing tests, but it seems only to bring us more in line with core Git, so I think those changes are acceptable. --- src/attr_file.c | 25 +++++++--- src/attr_file.h | 6 +-- src/diff.c | 40 ++-------------- src/ignore.c | 67 ++++++++++---------------- src/ignore.h | 9 +++- src/iterator.c | 66 +++++++++++++++++++------- src/iterator.h | 2 + tests/diff/iterator.c | 2 +- tests/status/ignore.c | 107 ++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 219 insertions(+), 105 deletions(-) diff --git a/src/attr_file.c b/src/attr_file.c index 156a23d91..3e95a2134 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -281,7 +281,7 @@ uint32_t git_attr_file__name_hash(const char *name) int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value) { @@ -342,14 +342,11 @@ int git_attr_file__load_standalone(git_attr_file **out, const char *path) bool git_attr_fnmatch__match( git_attr_fnmatch *match, - const git_attr_path *path) + git_attr_path *path) { const char *filename; int flags = 0; - if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) - return false; - if (match->flags & GIT_ATTR_FNMATCH_ICASE) flags |= FNM_CASEFOLD; if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR) @@ -365,12 +362,28 @@ bool git_attr_fnmatch__match( flags |= FNM_LEADING_DIR; } + if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { + int matchval; + + /* for attribute checks or root ignore checks, fail match */ + if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) || + path->basename == path->path) + return false; + + /* for ignore checks, use container of current item for check */ + path->basename[-1] = '\0'; + flags |= FNM_LEADING_DIR; + matchval = p_fnmatch(match->pattern, path->path, flags); + path->basename[-1] = '/'; + return (matchval != FNM_NOMATCH); + } + return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); } bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path) + git_attr_path *path) { bool matched = git_attr_fnmatch__match(&rule->match, path); diff --git a/src/attr_file.h b/src/attr_file.h index e50aec07c..87cde7e35 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -138,7 +138,7 @@ int git_attr_file__clear_rules( int git_attr_file__lookup_one( git_attr_file *file, - const git_attr_path *path, + git_attr_path *path, const char *attr, const char **value); @@ -162,13 +162,13 @@ extern int git_attr_fnmatch__parse( extern bool git_attr_fnmatch__match( git_attr_fnmatch *rule, - const git_attr_path *path); + git_attr_path *path); extern void git_attr_rule__free(git_attr_rule *rule); extern bool git_attr_rule__match( git_attr_rule *rule, - const git_attr_path *path); + git_attr_path *path); extern git_attr_assignment *git_attr_rule__lookup_assignment( git_attr_rule *rule, const char *name); diff --git a/src/diff.c b/src/diff.c index 781f23ec6..0864dfc40 100644 --- a/src/diff.c +++ b/src/diff.c @@ -632,7 +632,6 @@ typedef struct { git_iterator *new_iter; const git_index_entry *oitem; const git_index_entry *nitem; - git_buf ignore_prefix; } diff_in_progress; #define MODE_BITS_MASK 0000777 @@ -843,24 +842,13 @@ static int handle_unmatched_new_item( /* check if this is a prefix of the other side */ contains_oitem = entry_is_prefixed(diff, info->oitem, nitem); - /* check if this is contained in an ignored parent directory */ - if (git_buf_len(&info->ignore_prefix)) { - if (diff->pfxcomp(nitem->path, git_buf_cstr(&info->ignore_prefix)) == 0) - delta_type = GIT_DELTA_IGNORED; - else - git_buf_clear(&info->ignore_prefix); - } + /* update delta_type if this item is ignored */ + if (git_iterator_current_is_ignored(info->new_iter)) + delta_type = GIT_DELTA_IGNORED; if (nitem->mode == GIT_FILEMODE_TREE) { bool recurse_into_dir = contains_oitem; - /* if not already inside an ignored dir, check if this is ignored */ - if (delta_type != GIT_DELTA_IGNORED && - git_iterator_current_is_ignored(info->new_iter)) { - delta_type = GIT_DELTA_IGNORED; - git_buf_sets(&info->ignore_prefix, nitem->path); - } - /* check if user requests recursion into this type of dir */ recurse_into_dir = contains_oitem || (delta_type == GIT_DELTA_UNTRACKED && @@ -937,27 +925,12 @@ static int handle_unmatched_new_item( } } - /* In core git, the next two checks are effectively reversed -- - * i.e. when an file contained in an ignored directory is explicitly - * ignored, it shows up as an ignored file in the diff list, even though - * other untracked files in the same directory are skipped completely. - * - * To me, this seems odd. If the directory is ignored and the file is - * untracked, we should skip it consistently, regardless of whether it - * happens to match a pattern in the ignore file. - * - * To match the core git behavior, reverse the following two if checks - * so that individual file ignores are checked before container - * directory exclusions are used to skip the file. - */ else if (delta_type == GIT_DELTA_IGNORED && - DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS)) + DIFF_FLAG_ISNT_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS) && + git_iterator_current_tree_is_ignored(info->new_iter)) /* item contained in ignored directory, so skip over it */ return git_iterator_advance(&info->nitem, info->new_iter); - else if (git_iterator_current_is_ignored(info->new_iter)) - delta_type = GIT_DELTA_IGNORED; - else if (info->new_iter->type != GIT_ITERATOR_TYPE_WORKDIR) delta_type = GIT_DELTA_ADDED; @@ -1067,7 +1040,6 @@ int git_diff__from_iterators( info.repo = repo; info.old_iter = old_iter; info.new_iter = new_iter; - git_buf_init(&info.ignore_prefix, 0); /* make iterators have matching icase behavior */ if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_CASE)) { @@ -1122,8 +1094,6 @@ cleanup: else git_diff_free(diff); - git_buf_free(&info.ignore_prefix); - return error; } diff --git a/src/ignore.c b/src/ignore.c index f373c9482..78f01ac44 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -248,14 +248,15 @@ void git_ignore__free(git_ignores *ignores) } static bool ignore_lookup_in_rules( - git_attr_file *file, git_attr_path *path, int *ignored) + int *ignored, git_attr_file *file, git_attr_path *path) { size_t j; git_attr_fnmatch *match; git_vector_rforeach(&file->rules, j, match) { if (git_attr_fnmatch__match(match, path)) { - *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0); + *ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ? + GIT_IGNORE_TRUE : GIT_IGNORE_FALSE; return true; } } @@ -264,34 +265,34 @@ static bool ignore_lookup_in_rules( } int git_ignore__lookup( - git_ignores *ignores, const char *pathname, int *ignored) + int *out, git_ignores *ignores, const char *pathname) { unsigned int i; git_attr_file *file; git_attr_path path; + *out = GIT_IGNORE_NOTFOUND; + if (git_attr_path__init( &path, pathname, git_repository_workdir(ignores->repo)) < 0) return -1; /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores->ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(out, ignores->ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores->ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores->ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(out, file, &path)) goto cleanup; } - *ignored = 0; - cleanup: git_attr_path__free(&path); return 0; @@ -335,8 +336,6 @@ int git_ignore_path_is_ignored( int error; const char *workdir; git_attr_path path; - char *tail, *end; - bool full_is_dir; git_ignores ignores; unsigned int i; git_attr_file *file; @@ -345,55 +344,42 @@ int git_ignore_path_is_ignored( workdir = repo ? git_repository_workdir(repo) : NULL; - if ((error = git_attr_path__init(&path, pathname, workdir)) < 0) - return error; + memset(&path, 0, sizeof(path)); + memset(&ignores, 0, sizeof(ignores)); - tail = path.path; - end = &path.full.ptr[path.full.size]; - full_is_dir = path.is_dir; + if ((error = git_attr_path__init(&path, pathname, workdir)) < 0 || + (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) + goto cleanup; while (1) { - /* advance to next component of path */ - path.basename = tail; - - while (tail < end && *tail != '/') tail++; - *tail = '\0'; - - path.full.size = (tail - path.full.ptr); - path.is_dir = (tail == end) ? full_is_dir : true; - - /* initialize ignores the first time through */ - if (path.basename == path.path && - (error = git_ignore__for_path(repo, path.path, &ignores)) < 0) - break; - /* first process builtins - success means path was found */ - if (ignore_lookup_in_rules(ignores.ign_internal, &path, ignored)) + if (ignore_lookup_in_rules(ignored, ignores.ign_internal, &path)) goto cleanup; /* next process files in the path */ git_vector_foreach(&ignores.ign_path, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } /* last process global ignores */ git_vector_foreach(&ignores.ign_global, i, file) { - if (ignore_lookup_in_rules(file, &path, ignored)) + if (ignore_lookup_in_rules(ignored, file, &path)) goto cleanup; } - /* if we found no rules before reaching the end, we're done */ - if (tail == end) + /* move up one directory */ + if (path.basename == path.path) break; + path.basename[-1] = '\0'; + while (path.basename > path.path && *path.basename != '/') + path.basename--; + if (path.basename > path.path) + path.basename++; + path.is_dir = 1; - /* now add this directory to list of ignores */ - if ((error = git_ignore__push_dir(&ignores, path.path)) < 0) + if ((error = git_ignore__pop_dir(&ignores)) < 0) break; - - /* reinstate divider in path */ - *tail = '/'; - while (*tail == '/') tail++; } *ignored = 0; @@ -404,7 +390,6 @@ cleanup: return error; } - int git_ignore__check_pathspec_for_exact_ignores( git_repository *repo, git_vector *vspec, diff --git a/src/ignore.h b/src/ignore.h index ff9369000..77668c661 100644 --- a/src/ignore.h +++ b/src/ignore.h @@ -42,7 +42,14 @@ extern int git_ignore__pop_dir(git_ignores *ign); extern void git_ignore__free(git_ignores *ign); -extern int git_ignore__lookup(git_ignores *ign, const char *path, int *ignored); +enum { + GIT_IGNORE_UNCHECKED = -2, + GIT_IGNORE_NOTFOUND = -1, + GIT_IGNORE_FALSE = 0, + GIT_IGNORE_TRUE = 1, +}; + +extern int git_ignore__lookup(int *out, git_ignores *ign, const char *path); /* command line Git sometimes generates an error message if given a * pathspec that contains an exact match to an ignored file (provided diff --git a/src/iterator.c b/src/iterator.c index 4f8087c8d..c664f17cd 100644 --- a/src/iterator.c +++ b/src/iterator.c @@ -897,6 +897,7 @@ struct fs_iterator_frame { fs_iterator_frame *next; git_vector entries; size_t index; + int is_ignored; }; typedef struct fs_iterator fs_iterator; @@ -1290,16 +1291,28 @@ GIT_INLINE(bool) workdir_path_is_dotgit(const git_buf *path) static int workdir_iterator__enter_dir(fs_iterator *fi) { + workdir_iterator *wi = (workdir_iterator *)fi; fs_iterator_frame *ff = fi->stack; size_t pos; git_path_with_stat *entry; bool found_submodules = false; - /* only push new ignores if this is not top level directory */ + /* check if this directory is ignored */ + if (git_ignore__lookup( + &ff->is_ignored, &wi->ignores, fi->path.ptr + fi->root_len) < 0) { + giterr_clear(); + ff->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* if this is not the top level directory... */ if (ff->next != NULL) { - workdir_iterator *wi = (workdir_iterator *)fi; ssize_t slash_pos = git_buf_rfind_next(&fi->path, '/'); + /* inherit ignored from parent if no rule specified */ + if (ff->is_ignored <= GIT_IGNORE_NOTFOUND) + ff->is_ignored = ff->next->is_ignored; + + /* push new ignores for files in this directory */ (void)git_ignore__push_dir(&wi->ignores, &fi->path.ptr[slash_pos + 1]); } @@ -1342,7 +1355,7 @@ static int workdir_iterator__update_entry(fs_iterator *fi) return GIT_ENOTFOUND; /* reset is_ignored since we haven't checked yet */ - wi->is_ignored = -1; + wi->is_ignored = GIT_IGNORE_UNCHECKED; return 0; } @@ -1487,6 +1500,19 @@ int git_iterator_current_parent_tree( return 0; } +static void workdir_iterator_update_is_ignored(workdir_iterator *wi) +{ + if (git_ignore__lookup( + &wi->is_ignored, &wi->ignores, wi->fi.entry.path) < 0) { + giterr_clear(); + wi->is_ignored = GIT_IGNORE_NOTFOUND; + } + + /* use ignore from containing frame stack */ + if (wi->is_ignored <= GIT_IGNORE_NOTFOUND) + wi->is_ignored = wi->fi.stack->is_ignored; +} + bool git_iterator_current_is_ignored(git_iterator *iter) { workdir_iterator *wi = (workdir_iterator *)iter; @@ -1494,14 +1520,22 @@ bool git_iterator_current_is_ignored(git_iterator *iter) if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) return false; - if (wi->is_ignored != -1) - return (bool)(wi->is_ignored != 0); + if (wi->is_ignored != GIT_IGNORE_UNCHECKED) + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); - return (bool)wi->is_ignored; + return (bool)(wi->is_ignored == GIT_IGNORE_TRUE); +} + +bool git_iterator_current_tree_is_ignored(git_iterator *iter) +{ + workdir_iterator *wi = (workdir_iterator *)iter; + + if (iter->type != GIT_ITERATOR_TYPE_WORKDIR) + return false; + + return (bool)(wi->fi.stack->is_ignored == GIT_IGNORE_TRUE); } int git_iterator_cmp(git_iterator *iter, const char *path_prefix) @@ -1549,10 +1583,8 @@ int git_iterator_advance_over_with_status( return error; if (!S_ISDIR(entry->mode)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; - if (wi->is_ignored) + workdir_iterator_update_is_ignored(wi); + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; return git_iterator_advance(entryptr, iter); } @@ -1564,14 +1596,12 @@ int git_iterator_advance_over_with_status( /* scan inside directory looking for a non-ignored item */ while (entry && !iter->prefixcomp(entry->path, base)) { - if (git_ignore__lookup( - &wi->ignores, wi->fi.entry.path, &wi->is_ignored) < 0) - wi->is_ignored = true; + workdir_iterator_update_is_ignored(wi); /* if we found an explicitly ignored item, then update from * EMPTY to IGNORED */ - if (wi->is_ignored) + if (wi->is_ignored == GIT_IGNORE_TRUE) *status = GIT_ITERATOR_STATUS_IGNORED; else if (S_ISDIR(entry->mode)) { error = git_iterator_advance_into(&entry, iter); @@ -1580,7 +1610,7 @@ int git_iterator_advance_over_with_status( continue; else if (error == GIT_ENOTFOUND) { error = 0; - wi->is_ignored = true; /* mark empty directories as ignored */ + 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 f67830212..d88ad5191 100644 --- a/src/iterator.h +++ b/src/iterator.h @@ -245,6 +245,8 @@ extern int git_iterator_current_parent_tree( extern bool git_iterator_current_is_ignored(git_iterator *iter); +extern bool git_iterator_current_tree_is_ignored(git_iterator *iter); + extern int git_iterator_cmp( git_iterator *iter, const char *path_prefix); diff --git a/tests/diff/iterator.c b/tests/diff/iterator.c index cdc64eb1d..a2df1c7a7 100644 --- a/tests/diff/iterator.c +++ b/tests/diff/iterator.c @@ -647,7 +647,7 @@ static void workdir_iterator_test( void test_diff_iterator__workdir_0(void) { - workdir_iterator_test("attr", NULL, NULL, 27, 1, NULL, "ign"); + workdir_iterator_test("attr", NULL, NULL, 23, 5, NULL, "ign"); } static const char *status_paths[] = { diff --git a/tests/status/ignore.c b/tests/status/ignore.c index caa1f1927..a4e766fdf 100644 --- a/tests/status/ignore.c +++ b/tests/status/ignore.c @@ -681,3 +681,110 @@ void test_status_ignore__issue_1766_negated_ignores(void) } } +static void add_one_to_index(const char *file) +{ + git_index *index; + cl_git_pass(git_repository_index(&index, g_repo)); + cl_git_pass(git_index_add_bypath(index, file)); + git_index_free(index); +} + +/* Some further broken scenarios that have been reported */ +void test_status_ignore__more_breakage(void) +{ + static const char *test_files[] = { + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/tracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/d5/untracked", + "empty_standard_repo/d1/pfx-d2/d3/d4/untracked", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "/d1/pfx-*\n" + "!/d1/pfx-d2/\n" + "/d1/pfx-d2/*\n" + "!/d1/pfx-d2/d3/\n" + "/d1/pfx-d2/d3/*\n" + "!/d1/pfx-d2/d3/d4/\n"); + add_one_to_index("d1/pfx-d2/d3/d4/d5/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", + "d1/pfx-d2/d3/d4/d5/tracked", + "d1/pfx-d2/d3/d4/d5/untracked", + "d1/pfx-d2/d3/d4/untracked", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, + GIT_STATUS_INDEX_NEW, + GIT_STATUS_WT_NEW, + GIT_STATUS_WT_NEW, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 4; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + refute_is_ignored("d1/pfx-d2/d3/d4/d5/tracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/d5/untracked"); + refute_is_ignored("d1/pfx-d2/d3/d4/untracked"); +} + +void test_status_ignore__negative_ignores_inside_ignores(void) +{ + static const char *test_files[] = { + "empty_standard_repo/top/mid/btm/tracked", + "empty_standard_repo/top/mid/btm/untracked", + NULL + }; + + make_test_data("empty_standard_repo", test_files); + cl_git_mkfile( + "empty_standard_repo/.gitignore", + "top\n!top/mid/btm\n"); + add_one_to_index("top/mid/btm/tracked"); + + { + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + status_entry_counts counts; + static const char *files[] = { + ".gitignore", "top/mid/btm/tracked", "top/mid/btm/untracked", + }; + static const unsigned int statuses[] = { + GIT_STATUS_WT_NEW, GIT_STATUS_INDEX_NEW, GIT_STATUS_WT_NEW, + }; + + memset(&counts, 0x0, sizeof(status_entry_counts)); + counts.expected_entry_count = 3; + counts.expected_paths = files; + counts.expected_statuses = statuses; + opts.flags = GIT_STATUS_OPT_DEFAULTS | + GIT_STATUS_OPT_INCLUDE_IGNORED | + GIT_STATUS_OPT_RECURSE_IGNORED_DIRS; + cl_git_pass(git_status_foreach_ext( + g_repo, &opts, cb_status__normal, &counts)); + + cl_assert_equal_i(counts.expected_entry_count, counts.entry_count); + cl_assert_equal_i(0, counts.wrong_status_flags_count); + cl_assert_equal_i(0, counts.wrong_sorted_path); + } + + refute_is_ignored("top/mid/btm/tracked"); + refute_is_ignored("top/mid/btm/untracked"); +} From 86d5810b82965224b1270c3627da029588935abb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 6 May 2014 16:20:14 +0200 Subject: [PATCH 08/95] pack: remove misleading comment --- src/pack.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pack.c b/src/pack.c index de038a45c..9c6a4e78b 100644 --- a/src/pack.c +++ b/src/pack.c @@ -546,13 +546,6 @@ static int packfile_unpack_delta( if (!cached) { /* have to inflate it */ error = git_packfile_unpack(&base, p, &base_offset); - - /* - * TODO: git.git tries to load the base from other packfiles - * or loose objects. - * - * We'll need to do this in order to support thin packs. - */ if (error < 0) return error; } From ae0817393c0aaff5c4b085c46ed11acc0ab64198 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 6 May 2014 21:21:04 +0200 Subject: [PATCH 09/95] pack: do not repeat the same error message four times Repeating this error message makes it harder to find out where we actually are finding the error, and they don't really describe what we're trying to do. --- src/pack.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pack.c b/src/pack.c index 9c6a4e78b..b9374d04f 100644 --- a/src/pack.c +++ b/src/pack.c @@ -653,7 +653,7 @@ int git_packfile_stream_open(git_packfile_stream *obj, struct git_pack_file *p, st = inflateInit(&obj->zstream); if (st != Z_OK) { git__free(obj); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init packfile stream"); return -1; } @@ -684,7 +684,7 @@ ssize_t git_packfile_stream_read(git_packfile_stream *obj, void *buffer, size_t written = len - obj->zstream.avail_out; if (st != Z_OK && st != Z_STREAM_END) { - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error reading from the zlib stream"); return -1; } @@ -729,7 +729,7 @@ int packfile_unpack_compressed( st = inflateInit(&stream); if (st != Z_OK) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "failed to init zlib stream on unpack"); return -1; } @@ -756,7 +756,7 @@ int packfile_unpack_compressed( if ((st != Z_STREAM_END) || stream.total_out != size) { git__free(buffer); - giterr_set(GITERR_ZLIB, "Failed to inflate packfile"); + giterr_set(GITERR_ZLIB, "error inflating zlib stream"); return -1; } From 2acdf4b854bf55ba2630c7342d09b136d919d6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 6 May 2014 19:20:33 +0200 Subject: [PATCH 10/95] pack: unpack using a loop We currently make use of recursive function calls to unpack an object, resolving the deltas as we come back down the chain. This means that we have unbounded stack growth as we look up objects in a pack. This is now done in two steps: first we figure out what the dependency chain is by looking up the delta bases until we reach a non-delta object, pushing the information we need onto a stack and then we pop from that stack and apply the deltas until there are no more left. This version of the code does not make use of the delta base cache so it is slower than what's in the mainline. A later commit will reintroduce it. --- src/pack.c | 140 ++++++++++++++++++++++++++++++++++++++++++++--------- src/pack.h | 9 ++++ 2 files changed, 126 insertions(+), 23 deletions(-) diff --git a/src/pack.c b/src/pack.c index b9374d04f..523905f8f 100644 --- a/src/pack.c +++ b/src/pack.c @@ -40,6 +40,13 @@ static int pack_entry_find_offset( const git_oid *short_oid, size_t len); +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. As we use a stack, the latest is the base object, + * the rest are deltas. + */ +static int pack_dependency_chain(git_dependency_chain *chain, struct git_pack_file *p, git_off_t off); + static int packfile_error(const char *message) { giterr_set(GITERR_ODB, "Invalid pack file - %s", message); @@ -583,47 +590,63 @@ int git_packfile_unpack( git_mwindow *w_curs = NULL; git_off_t curpos = *obj_offset; int error; + git_dependency_chain chain; + struct pack_chain_elem *elem; - size_t size = 0; - git_otype type; + git_otype base_type; /* * TODO: optionally check the CRC on the packfile */ + error = pack_dependency_chain(&chain, p, *obj_offset); + if (error < 0) + return error; + obj->data = NULL; obj->len = 0; obj->type = GIT_OBJ_BAD; - error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + /* the first one is the base, so we expand that one */ + elem = git_array_pop(chain); + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); git_mwindow_close(&w_curs); if (error < 0) - return error; + goto cleanup; - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - error = packfile_unpack_delta( - obj, p, &w_curs, &curpos, - size, type, *obj_offset); - break; + base_type = elem->type; + /* we now apply each consecutive delta until we run out */ + while (git_array_size(chain) > 0) { + git_rawobj base, delta; - case GIT_OBJ_COMMIT: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_TAG: - error = packfile_unpack_compressed( - obj, p, &w_curs, &curpos, - size, type); - break; + elem = git_array_pop(chain); + curpos = elem->offset; + error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); - default: - error = packfile_error("invalid packfile type in header");; - break; + if (error < 0) + break; + + /* the current object becomes the new base, on which we apply the delta */ + base = *obj; + obj->data = NULL; + obj->len = 0; + obj->type = GIT_OBJ_BAD; + + error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + git__free(delta.data); + git__free(base.data); + + if (error < 0) + break; + + obj->type = base_type; } - *obj_offset = curpos; +cleanup: + git_array_clear(chain); return error; } @@ -1215,3 +1238,74 @@ int git_pack_entry_find( git_oid_cpy(&e->sha1, &found_oid); return 0; } + +static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pack_file *p, git_off_t obj_offset) +{ + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + git_off_t curpos = obj_offset, base_offset; + int error = 0, found_base = 0; + size_t size; + git_otype type; + + while (!found_base && error == 0) { + struct pack_chain_elem *elem; + + curpos = obj_offset; + elem = git_array_alloc(chain); + if (!elem) + return -1; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); + + if (error < 0) + return error; + + elem->offset = curpos; + elem->size = size; + elem->type = type; + + switch (type) { + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); + + if (base_offset == 0) + return packfile_error("delta offset is zero"); + if (base_offset < 0) /* must actually be an error code */ + return (int)base_offset; + + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + break; + + /* one of these means we've found the final object in the chain */ + case GIT_OBJ_COMMIT: + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + case GIT_OBJ_TAG: + found_base = 1; + break; + + default: + error = packfile_error("invalid packfile type in header"); + break; + } + } + + if (!found_base) { + git_array_clear(chain); + return packfile_error("after dependency chain loop; cannot happen"); + } + + if (error < 0) + git_array_clear(chain); + + *chain_out = chain; + return error; +} diff --git a/src/pack.h b/src/pack.h index 58f81e2f0..a2ea3849f 100644 --- a/src/pack.h +++ b/src/pack.h @@ -17,6 +17,7 @@ #include "mwindow.h" #include "odb.h" #include "oidmap.h" +#include "array.h" #define GIT_PACK_FILE_MODE 0444 @@ -60,6 +61,14 @@ typedef struct git_pack_cache_entry { git_rawobj raw; } git_pack_cache_entry; +struct pack_chain_elem { + git_off_t offset; + size_t size; + git_otype type; +}; + +typedef git_array_t(struct pack_chain_elem) git_dependency_chain; + #include "offmap.h" GIT__USE_OFFMAP; From a332e91c92524cc21818eadfbe723361d31dc187 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 6 May 2014 23:37:28 +0200 Subject: [PATCH 11/95] pack: use a cache for delta bases when unpacking Bring back the use of the delta base cache for unpacking objects. When generating the delta chain, we stop when we find a delta base in the pack's cache and use that as the starting point. --- src/pack.c | 145 ++++++++++++++++++++++++++--------------------------- src/pack.h | 5 ++ 2 files changed, 77 insertions(+), 73 deletions(-) diff --git a/src/pack.c b/src/pack.c index 523905f8f..c1d7592fd 100644 --- a/src/pack.c +++ b/src/pack.c @@ -42,8 +42,9 @@ static int pack_entry_find_offset( /** * Generate the chain of dependencies which we need to get to the - * object at `off`. As we use a stack, the latest is the base object, - * the rest are deltas. + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. */ static int pack_dependency_chain(git_dependency_chain *chain, struct git_pack_file *p, git_off_t off); @@ -521,67 +522,6 @@ int git_packfile_resolve_header( return error; } -static int packfile_unpack_delta( - git_rawobj *obj, - struct git_pack_file *p, - git_mwindow **w_curs, - git_off_t *curpos, - size_t delta_size, - git_otype delta_type, - git_off_t obj_offset) -{ - git_off_t base_offset, base_key; - git_rawobj base, delta; - git_pack_cache_entry *cached = NULL; - int error, found_base = 0; - - base_offset = get_delta_base(p, w_curs, curpos, delta_type, obj_offset); - git_mwindow_close(w_curs); - if (base_offset == 0) - return packfile_error("delta offset is zero"); - if (base_offset < 0) /* must actually be an error code */ - return (int)base_offset; - - if (!p->bases.entries && (cache_init(&p->bases) < 0)) - return -1; - - base_key = base_offset; /* git_packfile_unpack modifies base_offset */ - if ((cached = cache_get(&p->bases, base_offset)) != NULL) { - memcpy(&base, &cached->raw, sizeof(git_rawobj)); - found_base = 1; - } - - if (!cached) { /* have to inflate it */ - error = git_packfile_unpack(&base, p, &base_offset); - if (error < 0) - return error; - } - - error = packfile_unpack_compressed(&delta, p, w_curs, curpos, delta_size, delta_type); - git_mwindow_close(w_curs); - - if (error < 0) { - if (!found_base) - git__free(base.data); - return error; - } - - obj->type = base.type; - error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); - if (error < 0) - goto on_error; - - if (found_base) - git_atomic_dec(&cached->refcount); - else if (cache_add(&p->bases, &base, base_key) < 0) - git__free(base.data); - -on_error: - git__free(delta.data); - - return error; /* error set by git__delta_apply */ -} - int git_packfile_unpack( git_rawobj *obj, struct git_pack_file *p, @@ -589,10 +529,10 @@ int git_packfile_unpack( { git_mwindow *w_curs = NULL; git_off_t curpos = *obj_offset; - int error; - git_dependency_chain chain; + int error, free_base = 0; + git_dependency_chain chain = GIT_ARRAY_INIT; struct pack_chain_elem *elem; - + git_pack_cache_entry *cached = NULL; git_otype base_type; /* @@ -609,16 +549,38 @@ int git_packfile_unpack( /* the first one is the base, so we expand that one */ elem = git_array_pop(chain); - curpos = elem->offset; - error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); + if (elem->cached) { + cached = elem->cached_entry; + memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + } else { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + base_type = elem->type; + free_base = 1; + } if (error < 0) goto cleanup; - base_type = elem->type; + /* + * Finding the object we want as the base element is + * problematic, as we need to make sure we don't accidentally + * give the caller the cached object, which it would then feel + * free to free, so we need to copy the data. + */ + if (cached && git_array_size(chain) == 0) { + void *data = obj->data; + obj->data = git__malloc(obj->len + 1); + GITERR_CHECK_ALLOC(obj->data); + memcpy(obj->data, data, obj->len + 1); + git_atomic_dec(&cached->refcount); + goto cleanup; + } + /* we now apply each consecutive delta until we run out */ - while (git_array_size(chain) > 0) { + while (git_array_size(chain) > 0 && !error) { git_rawobj base, delta; elem = git_array_pop(chain); @@ -636,16 +598,39 @@ int git_packfile_unpack( obj->type = GIT_OBJ_BAD; error = git__delta_apply(obj, base.data, base.len, delta.data, delta.len); + obj->type = base_type; + /* + * We usually don't want to free the base at this + * point, as we put it into the cache in the previous + * iteration. free_base lets us know that we got the + * base object directly from the packfile, so we can free it. + */ git__free(delta.data); - git__free(base.data); + if (free_base) { + free_base = 0; + git__free(base.data); + } + + if (cached) { + git_atomic_dec(&cached->refcount); + cached = NULL; + } if (error < 0) break; - obj->type = base_type; + /* only try to cache if we're not handing this buffer off to the caller */ + if (git_array_size(chain) > 0 && + (error = cache_add(&p->bases, obj, elem->base_key)) < 0) + goto cleanup; } cleanup: + if (error < 0) + git__free(obj->data); + + *obj_offset = elem->offset; + git_array_clear(chain); return error; } @@ -1248,8 +1233,12 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac size_t size; git_otype type; + if (!p->bases.entries && (cache_init(&p->bases) < 0)) + return -1; + while (!found_base && error == 0) { struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; curpos = obj_offset; elem = git_array_alloc(chain); @@ -1262,13 +1251,23 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac if (error < 0) return error; + elem->cached = 0; elem->offset = curpos; elem->size = size; elem->type = type; + elem->base_key = obj_offset; switch (type) { case GIT_OBJ_OFS_DELTA: case GIT_OBJ_REF_DELTA: + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + elem->cached_entry = cached; + elem->cached = 1; + found_base = 1; + break; + } + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); git_mwindow_close(&w_curs); diff --git a/src/pack.h b/src/pack.h index a2ea3849f..e86889d1b 100644 --- a/src/pack.h +++ b/src/pack.h @@ -62,9 +62,14 @@ typedef struct git_pack_cache_entry { } git_pack_cache_entry; struct pack_chain_elem { + int cached; + git_off_t base_key; + /* if we don't have it cached we have this */ git_off_t offset; size_t size; git_otype type; + /* if cached, we have this instead */ + git_pack_cache_entry *cached_entry; }; typedef git_array_t(struct pack_chain_elem) git_dependency_chain; From e6d10c58b547181fe19f6bacff6bd0dee9f67b9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 May 2014 16:24:54 +0200 Subject: [PATCH 12/95] pack: make sure not to leak the dep chain --- src/pack.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/pack.c b/src/pack.c index c1d7592fd..a8577d389 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1242,14 +1242,16 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac curpos = obj_offset; elem = git_array_alloc(chain); - if (!elem) - return -1; + if (!elem) { + error = -1; + goto on_error; + } error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); git_mwindow_close(&w_curs); if (error < 0) - return error; + goto on_error; elem->cached = 0; elem->offset = curpos; @@ -1273,8 +1275,10 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac if (base_offset == 0) return packfile_error("delta offset is zero"); - if (base_offset < 0) /* must actually be an error code */ - return (int)base_offset; + if (base_offset < 0) { /* must actually be an error code */ + error = (int)base_offset; + goto on_error; + } /* we need to pass the pos *after* the delta-base bit */ elem->offset = curpos; @@ -1302,9 +1306,10 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac return packfile_error("after dependency chain loop; cannot happen"); } - if (error < 0) - git_array_clear(chain); - *chain_out = chain; return error; + +on_error: + git_array_clear(chain); + return error; } From b2559f477a3f8e2bc76140ca2c76d8cc30b5f5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 8 May 2014 17:14:59 +0200 Subject: [PATCH 13/95] pack: preallocate a 64-element chain Dependency chains are often large and require a few reallocations. Allocate a 64-element chain before doing anything else to avoid allocations during the loop. This value comes from the stack-allocated one git uses. We still allocate this on the heap, but it does help performance a little bit. --- src/pack.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pack.c b/src/pack.c index a8577d389..664e8efb0 100644 --- a/src/pack.c +++ b/src/pack.c @@ -1236,6 +1236,7 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac if (!p->bases.entries && (cache_init(&p->bases) < 0)) return -1; + git_array_init_to_size(chain, 64); while (!found_base && error == 0) { struct pack_chain_elem *elem; git_pack_cache_entry *cached = NULL; From 9dbd150f5f499bbf768d65c4ebb596651e77a1b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 9 May 2014 09:36:09 +0200 Subject: [PATCH 14/95] pack: simplify delta chain code The switch makes the loop somewhat unwieldy. Let's assume it's fine and perform the check when we're accessing the data. This makes our code look a lot more like git's. --- src/pack.c | 100 +++++++++++++++++++++++++++-------------------------- 1 file changed, 51 insertions(+), 49 deletions(-) diff --git a/src/pack.c b/src/pack.c index 664e8efb0..e1fd276b7 100644 --- a/src/pack.c +++ b/src/pack.c @@ -549,21 +549,38 @@ int git_packfile_unpack( /* the first one is the base, so we expand that one */ elem = git_array_pop(chain); + base_type = elem->type; if (elem->cached) { cached = elem->cached_entry; memcpy(obj, &cached->raw, sizeof(git_rawobj)); - base_type = obj->type; - } else { - curpos = elem->offset; - error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); - git_mwindow_close(&w_curs); - base_type = elem->type; - free_base = 1; } if (error < 0) goto cleanup; + switch (base_type) { + case GIT_OBJ_COMMIT: + case GIT_OBJ_TREE: + case GIT_OBJ_BLOB: + case GIT_OBJ_TAG: + if (!elem->cached) { + curpos = elem->offset; + error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); + git_mwindow_close(&w_curs); + free_base = 1; + } + if (error < 0) + goto cleanup; + break; + case GIT_OBJ_OFS_DELTA: + case GIT_OBJ_REF_DELTA: + error = packfile_error("dependency chain ends in a delta"); + goto cleanup; + default: + error = packfile_error("invalid packfile type in header"); + goto cleanup; + } + /* * Finding the object we want as the base element is * problematic, as we need to make sure we don't accidentally @@ -1229,7 +1246,7 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac git_dependency_chain chain = GIT_ARRAY_INIT; git_mwindow *w_curs = NULL; git_off_t curpos = obj_offset, base_offset; - int error = 0, found_base = 0; + int error = 0; size_t size; git_otype type; @@ -1237,7 +1254,7 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac return -1; git_array_init_to_size(chain, 64); - while (!found_base && error == 0) { + while (true) { struct pack_chain_elem *elem; git_pack_cache_entry *cached = NULL; @@ -1248,6 +1265,16 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac goto on_error; } + elem->base_key = obj_offset; + + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + elem->cached_entry = cached; + elem->cached = 1; + elem->type = cached->raw.type; + break; + } + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); git_mwindow_close(&w_curs); @@ -1260,51 +1287,26 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pac elem->type = type; elem->base_key = obj_offset; - switch (type) { - case GIT_OBJ_OFS_DELTA: - case GIT_OBJ_REF_DELTA: - /* if we have a base cached, we can stop here instead */ - if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { - elem->cached_entry = cached; - elem->cached = 1; - found_base = 1; - break; - } - - base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); - git_mwindow_close(&w_curs); - - if (base_offset == 0) - return packfile_error("delta offset is zero"); - if (base_offset < 0) { /* must actually be an error code */ - error = (int)base_offset; - goto on_error; - } - - /* we need to pass the pos *after* the delta-base bit */ - elem->offset = curpos; - - /* go through the loop again, but with the new object */ - obj_offset = base_offset; + if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA) break; - /* one of these means we've found the final object in the chain */ - case GIT_OBJ_COMMIT: - case GIT_OBJ_TREE: - case GIT_OBJ_BLOB: - case GIT_OBJ_TAG: - found_base = 1; - break; + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); - default: - error = packfile_error("invalid packfile type in header"); - break; + if (base_offset == 0) { + error = packfile_error("delta offset is zero"); + goto on_error; + } + if (base_offset < 0) { /* must actually be an error code */ + error = (int)base_offset; + goto on_error; } - } - if (!found_base) { - git_array_clear(chain); - return packfile_error("after dependency chain loop; cannot happen"); + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; } *chain_out = chain; From a3ffbf230e454309c96961a182520a53f555d356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 11 May 2014 03:50:34 +0200 Subject: [PATCH 15/95] pack: expose a cached delta base directly Instead of going through a special entry in the chain, let's pass it as an output parameter. --- src/pack.c | 185 ++++++++++++++++++++++++++--------------------------- src/pack.h | 4 -- 2 files changed, 92 insertions(+), 97 deletions(-) diff --git a/src/pack.c b/src/pack.c index e1fd276b7..b5e8febd4 100644 --- a/src/pack.c +++ b/src/pack.c @@ -40,14 +40,6 @@ static int pack_entry_find_offset( const git_oid *short_oid, size_t len); -/** - * Generate the chain of dependencies which we need to get to the - * object at `off`. `chain` is used a stack, popping gives the right - * order to apply deltas on. If an object is found in the pack's base - * cache, we stop calculating there. - */ -static int pack_dependency_chain(git_dependency_chain *chain, struct git_pack_file *p, git_off_t off); - static int packfile_error(const char *message) { giterr_set(GITERR_ODB, "Invalid pack file - %s", message); @@ -522,6 +514,87 @@ int git_packfile_resolve_header( return error; } +/** + * Generate the chain of dependencies which we need to get to the + * object at `off`. `chain` is used a stack, popping gives the right + * order to apply deltas on. If an object is found in the pack's base + * cache, we stop calculating there. + */ +static int pack_dependency_chain(git_dependency_chain *chain_out, git_pack_cache_entry **cached_out, + git_off_t *cached_off, struct git_pack_file *p, git_off_t obj_offset) +{ + git_dependency_chain chain = GIT_ARRAY_INIT; + git_mwindow *w_curs = NULL; + git_off_t curpos = obj_offset, base_offset; + int error = 0; + size_t size; + git_otype type; + + if (!p->bases.entries && (cache_init(&p->bases) < 0)) + return -1; + + git_array_init_to_size(chain, 64); + while (true) { + struct pack_chain_elem *elem; + git_pack_cache_entry *cached = NULL; + + /* if we have a base cached, we can stop here instead */ + if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { + *cached_out = cached; + *cached_off = obj_offset; + break; + } + + curpos = obj_offset; + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } + + elem->base_key = obj_offset; + + error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); + git_mwindow_close(&w_curs); + + if (error < 0) + goto on_error; + + elem->offset = curpos; + elem->size = size; + elem->type = type; + elem->base_key = obj_offset; + + if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA) + break; + + base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); + git_mwindow_close(&w_curs); + + if (base_offset == 0) { + error = packfile_error("delta offset is zero"); + goto on_error; + } + if (base_offset < 0) { /* must actually be an error code */ + error = (int)base_offset; + goto on_error; + } + + /* we need to pass the pos *after* the delta-base bit */ + elem->offset = curpos; + + /* go through the loop again, but with the new object */ + obj_offset = base_offset; + } + + *chain_out = chain; + return error; + +on_error: + git_array_clear(chain); + return error; +} + int git_packfile_unpack( git_rawobj *obj, struct git_pack_file *p, @@ -531,7 +604,7 @@ int git_packfile_unpack( git_off_t curpos = *obj_offset; int error, free_base = 0; git_dependency_chain chain = GIT_ARRAY_INIT; - struct pack_chain_elem *elem; + struct pack_chain_elem *elem = NULL; git_pack_cache_entry *cached = NULL; git_otype base_type; @@ -539,7 +612,7 @@ int git_packfile_unpack( * TODO: optionally check the CRC on the packfile */ - error = pack_dependency_chain(&chain, p, *obj_offset); + error = pack_dependency_chain(&chain, &cached, obj_offset, p, *obj_offset); if (error < 0) return error; @@ -547,12 +620,12 @@ int git_packfile_unpack( obj->len = 0; obj->type = GIT_OBJ_BAD; - /* the first one is the base, so we expand that one */ - elem = git_array_pop(chain); - base_type = elem->type; - if (elem->cached) { - cached = elem->cached_entry; + if (cached) { memcpy(obj, &cached->raw, sizeof(git_rawobj)); + base_type = obj->type; + } else { + elem = git_array_pop(chain); + base_type = elem->type; } if (error < 0) @@ -563,10 +636,11 @@ int git_packfile_unpack( case GIT_OBJ_TREE: case GIT_OBJ_BLOB: case GIT_OBJ_TAG: - if (!elem->cached) { + if (!cached) { curpos = elem->offset; error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); git_mwindow_close(&w_curs); + base_type = elem->type; free_base = 1; } if (error < 0) @@ -646,7 +720,8 @@ cleanup: if (error < 0) git__free(obj->data); - *obj_offset = elem->offset; + if (elem) + *obj_offset = elem->offset; git_array_clear(chain); return error; @@ -1240,79 +1315,3 @@ int git_pack_entry_find( git_oid_cpy(&e->sha1, &found_oid); return 0; } - -static int pack_dependency_chain(git_dependency_chain *chain_out, struct git_pack_file *p, git_off_t obj_offset) -{ - git_dependency_chain chain = GIT_ARRAY_INIT; - git_mwindow *w_curs = NULL; - git_off_t curpos = obj_offset, base_offset; - int error = 0; - size_t size; - git_otype type; - - if (!p->bases.entries && (cache_init(&p->bases) < 0)) - return -1; - - git_array_init_to_size(chain, 64); - while (true) { - struct pack_chain_elem *elem; - git_pack_cache_entry *cached = NULL; - - curpos = obj_offset; - elem = git_array_alloc(chain); - if (!elem) { - error = -1; - goto on_error; - } - - elem->base_key = obj_offset; - - /* if we have a base cached, we can stop here instead */ - if ((cached = cache_get(&p->bases, obj_offset)) != NULL) { - elem->cached_entry = cached; - elem->cached = 1; - elem->type = cached->raw.type; - break; - } - - error = git_packfile_unpack_header(&size, &type, &p->mwf, &w_curs, &curpos); - git_mwindow_close(&w_curs); - - if (error < 0) - goto on_error; - - elem->cached = 0; - elem->offset = curpos; - elem->size = size; - elem->type = type; - elem->base_key = obj_offset; - - if (type != GIT_OBJ_OFS_DELTA && type != GIT_OBJ_REF_DELTA) - break; - - base_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset); - git_mwindow_close(&w_curs); - - if (base_offset == 0) { - error = packfile_error("delta offset is zero"); - goto on_error; - } - if (base_offset < 0) { /* must actually be an error code */ - error = (int)base_offset; - goto on_error; - } - - /* we need to pass the pos *after* the delta-base bit */ - elem->offset = curpos; - - /* go through the loop again, but with the new object */ - obj_offset = base_offset; - } - - *chain_out = chain; - return error; - -on_error: - git_array_clear(chain); - return error; -} diff --git a/src/pack.h b/src/pack.h index e86889d1b..610e70c18 100644 --- a/src/pack.h +++ b/src/pack.h @@ -62,14 +62,10 @@ typedef struct git_pack_cache_entry { } git_pack_cache_entry; struct pack_chain_elem { - int cached; git_off_t base_key; - /* if we don't have it cached we have this */ git_off_t offset; size_t size; git_otype type; - /* if cached, we have this instead */ - git_pack_cache_entry *cached_entry; }; typedef git_array_t(struct pack_chain_elem) git_dependency_chain; From 15bcced22379857978d80539da5fdd8f4667ff95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 11 May 2014 05:31:22 +0200 Subject: [PATCH 16/95] pack: use stack allocation for smaller delta chains This avoid allocating the array on the heap for relatively small chains. The expected performance increase is sadly not really noticeable. --- src/pack.c | 61 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/src/pack.c b/src/pack.c index b5e8febd4..235e8d3ee 100644 --- a/src/pack.c +++ b/src/pack.c @@ -514,26 +514,30 @@ int git_packfile_resolve_header( return error; } +#define SMALL_STACK_SIZE 64 + /** * Generate the chain of dependencies which we need to get to the * object at `off`. `chain` is used a stack, popping gives the right * order to apply deltas on. If an object is found in the pack's base * cache, we stop calculating there. */ -static int pack_dependency_chain(git_dependency_chain *chain_out, git_pack_cache_entry **cached_out, - git_off_t *cached_off, struct git_pack_file *p, git_off_t obj_offset) +static int pack_dependency_chain(git_dependency_chain *chain_out, + git_pack_cache_entry **cached_out, git_off_t *cached_off, + struct pack_chain_elem *small_stack, size_t *stack_sz, + struct git_pack_file *p, git_off_t obj_offset) { git_dependency_chain chain = GIT_ARRAY_INIT; git_mwindow *w_curs = NULL; git_off_t curpos = obj_offset, base_offset; - int error = 0; - size_t size; + int error = 0, use_heap = 0; + size_t size, elem_pos; git_otype type; if (!p->bases.entries && (cache_init(&p->bases) < 0)) return -1; - git_array_init_to_size(chain, 64); + elem_pos = 0; while (true) { struct pack_chain_elem *elem; git_pack_cache_entry *cached = NULL; @@ -545,11 +549,24 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, git_pack_cache break; } + /* if we run out of space on the small stack, use the array */ + if (elem_pos == SMALL_STACK_SIZE) { + git_array_init_to_size(chain, elem_pos); + GITERR_CHECK_ARRAY(chain); + memcpy(chain.ptr, small_stack, elem_pos * sizeof(struct pack_chain_elem)); + chain.size = elem_pos; + use_heap = 1; + } + curpos = obj_offset; - elem = git_array_alloc(chain); - if (!elem) { - error = -1; - goto on_error; + if (!use_heap) { + elem = &small_stack[elem_pos]; + } else { + elem = git_array_alloc(chain); + if (!elem) { + error = -1; + goto on_error; + } } elem->base_key = obj_offset; @@ -585,8 +602,11 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, git_pack_cache /* go through the loop again, but with the new object */ obj_offset = base_offset; + elem_pos++; } + + *stack_sz = elem_pos + 1; *chain_out = chain; return error; @@ -604,15 +624,17 @@ int git_packfile_unpack( git_off_t curpos = *obj_offset; int error, free_base = 0; git_dependency_chain chain = GIT_ARRAY_INIT; - struct pack_chain_elem *elem = NULL; + struct pack_chain_elem *elem = NULL, *stack; git_pack_cache_entry *cached = NULL; + struct pack_chain_elem small_stack[SMALL_STACK_SIZE]; + size_t stack_size, elem_pos; git_otype base_type; /* * TODO: optionally check the CRC on the packfile */ - error = pack_dependency_chain(&chain, &cached, obj_offset, p, *obj_offset); + error = pack_dependency_chain(&chain, &cached, obj_offset, small_stack, &stack_size, p, *obj_offset); if (error < 0) return error; @@ -620,11 +642,16 @@ int git_packfile_unpack( obj->len = 0; obj->type = GIT_OBJ_BAD; + /* let's point to the right stack */ + stack = chain.ptr ? chain.ptr : small_stack; + + elem_pos = stack_size; if (cached) { memcpy(obj, &cached->raw, sizeof(git_rawobj)); base_type = obj->type; + elem_pos--; /* stack_size includes the base, which isn't actually there */ } else { - elem = git_array_pop(chain); + elem = &stack[--elem_pos]; base_type = elem->type; } @@ -661,7 +688,7 @@ int git_packfile_unpack( * give the caller the cached object, which it would then feel * free to free, so we need to copy the data. */ - if (cached && git_array_size(chain) == 0) { + if (cached && stack_size == 1) { void *data = obj->data; obj->data = git__malloc(obj->len + 1); GITERR_CHECK_ALLOC(obj->data); @@ -671,10 +698,10 @@ int git_packfile_unpack( } /* we now apply each consecutive delta until we run out */ - while (git_array_size(chain) > 0 && !error) { + while (elem_pos > 0 && !error) { git_rawobj base, delta; - elem = git_array_pop(chain); + elem = &stack[elem_pos - 1]; curpos = elem->offset; error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); git_mwindow_close(&w_curs); @@ -711,9 +738,11 @@ int git_packfile_unpack( break; /* only try to cache if we're not handing this buffer off to the caller */ - if (git_array_size(chain) > 0 && + if (elem_pos != 1 && (error = cache_add(&p->bases, obj, elem->base_key)) < 0) goto cleanup; + + elem_pos--; } cleanup: From c968ce2c2c49ca7a559ecb8f7014f777c3a8a5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 12 May 2014 02:01:05 +0200 Subject: [PATCH 17/95] pack: don't forget to cache the base object The base object is a good cache candidate, so we shouldn't forget to add it to the cache. --- src/pack.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/pack.c b/src/pack.c index 235e8d3ee..d93ee25f9 100644 --- a/src/pack.c +++ b/src/pack.c @@ -668,7 +668,6 @@ int git_packfile_unpack( error = packfile_unpack_compressed(obj, p, &w_curs, &curpos, elem->size, elem->type); git_mwindow_close(&w_curs); base_type = elem->type; - free_base = 1; } if (error < 0) goto cleanup; @@ -683,7 +682,7 @@ int git_packfile_unpack( } /* - * Finding the object we want as the base element is + * Finding the object we want a cached base element is * problematic, as we need to make sure we don't accidentally * give the caller the cached object, which it would then feel * free to free, so we need to copy the data. @@ -701,6 +700,13 @@ int git_packfile_unpack( while (elem_pos > 0 && !error) { git_rawobj base, delta; + /* + * We can now try to add the base to the cache, as + * long as it's not already the cached one. + */ + if (!cached) + free_base = !!cache_add(&p->bases, obj, elem->base_key); + elem = &stack[elem_pos - 1]; curpos = elem->offset; error = packfile_unpack_compressed(&delta, p, &w_curs, &curpos, elem->size, elem->type); @@ -737,11 +743,6 @@ int git_packfile_unpack( if (error < 0) break; - /* only try to cache if we're not handing this buffer off to the caller */ - if (elem_pos != 1 && - (error = cache_add(&p->bases, obj, elem->base_key)) < 0) - goto cleanup; - elem_pos--; } From 7c57cd97d814a39829a6b6e03473e693ea4df602 Mon Sep 17 00:00:00 2001 From: Albert Meltzer Date: Mon, 12 May 2014 20:25:44 -0700 Subject: [PATCH 18/95] Win32 fix for #2300. The code doesn't use SSL and a test requires it. --- src/netops.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/netops.c b/src/netops.c index 24092c17f..a0193a022 100644 --- a/src/netops.c +++ b/src/netops.c @@ -206,6 +206,8 @@ static int gitno_ssl_teardown(gitno_ssl *ssl) return ret; } +#endif + /* Match host names according to RFC 2818 rules */ int gitno__match_host(const char *pattern, const char *host) { @@ -256,6 +258,8 @@ static int check_host_name(const char *name, const char *host) return 0; } +#ifdef GIT_SSL + static int verify_server_cert(gitno_ssl *ssl, const char *host) { X509 *cert; From b3f27c43685c3fb492b03ccca7c57a0b5db217ab Mon Sep 17 00:00:00 2001 From: Linquize Date: Tue, 13 May 2014 21:08:50 +0800 Subject: [PATCH 19/95] Initialize local variable --- src/indexer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexer.c b/src/indexer.c index 3c8415c7c..68496ceea 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -703,7 +703,7 @@ static int fix_thin_pack(git_indexer *idx, git_transfer_progress *stats) size_t size; git_otype type; git_mwindow *w = NULL; - git_off_t curpos; + git_off_t curpos = 0; unsigned char *base_info; unsigned int left = 0; git_oid base; From 562516ecebeefa89595b23c5aa559551a8bb7c56 Mon Sep 17 00:00:00 2001 From: Stefan Widgren Date: Tue, 13 May 2014 22:43:59 +0200 Subject: [PATCH 20/95] Add R bindings to the README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8dd073430..b60e8a234 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,8 @@ Here are the bindings to libgit2 that are currently available: * GitPowerShell * Python * pygit2 +* R + * git2r * Ruby * Rugged * Vala From a37aa82ea6f952745c883065a86162343e438981 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 May 2014 15:54:23 -0700 Subject: [PATCH 21/95] Some coverity inspired cleanups --- src/config.c | 12 ++++++------ src/config_file.c | 8 +++++--- src/diff_driver.c | 10 +++++----- src/tag.c | 14 ++++++++------ 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/config.c b/src/config.c index 757ff0387..4bd27a875 100644 --- a/src/config.c +++ b/src/config.c @@ -153,19 +153,19 @@ int git_config_snapshot(git_config **out, git_config *in) git_config_backend *b; if ((error = internal->file->snapshot(&b, internal->file)) < 0) - goto on_error; + break; if ((error = git_config_add_backend(config, b, internal->level, 0)) < 0) { b->free(b); - goto on_error; + break; } } - *out = config; - return error; + if (error < 0) + git_config_free(config); + else + *out = config; -on_error: - git_config_free(config); return error; } diff --git a/src/config_file.c b/src/config_file.c index b00d0827d..fdc9bc8a2 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -313,6 +313,7 @@ static int config__refresh(git_config_backend *cfg) goto out; reader = git_array_get(b->readers, git_array_size(b->readers) - 1); + GITERR_CHECK_ALLOC(reader); if ((error = config_parse(values->values, b, reader, b->level, 0)) < 0) goto out; @@ -327,7 +328,8 @@ static int config__refresh(git_config_backend *cfg) out: refcounted_strmap_free(values); - git_buf_free(&reader->buffer); + if (reader) + git_buf_free(&reader->buffer); return error; } @@ -344,8 +346,8 @@ static int config_refresh(git_config_backend *cfg) &reader->buffer, reader->file_path, &reader->file_mtime, &reader->file_size, &updated); - if (error < 0) - return (error == GIT_ENOTFOUND) ? 0 : error; + if (error < 0 && error != GIT_ENOTFOUND) + return error; if (updated) any_updated = 1; diff --git a/src/diff_driver.c b/src/diff_driver.c index fc1354f36..dc8e79e25 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -233,17 +233,17 @@ static int git_diff_driver_load( return 0; } + drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); + GITERR_CHECK_ALLOC(drv); + drv->type = DIFF_DRIVER_AUTO; + memcpy(drv->name, driver_name, namelen); + /* if you can't read config for repo, just use default driver */ if (git_repository_config_snapshot(&cfg, repo) < 0) { giterr_clear(); goto done; } - drv = git__calloc(1, sizeof(git_diff_driver) + namelen + 1); - GITERR_CHECK_ALLOC(drv); - drv->type = DIFF_DRIVER_AUTO; - memcpy(drv->name, driver_name, namelen); - if ((error = git_buf_printf(&name, "diff.%s.binary", driver_name)) < 0) goto done; diff --git a/src/tag.c b/src/tag.c index 1a4ee1e1c..d7b531d34 100644 --- a/src/tag.c +++ b/src/tag.c @@ -363,20 +363,22 @@ int git_tag_create_frombuffer(git_oid *oid, git_repository *repo, const char *bu } /* write the buffer */ - if (git_odb_open_wstream(&stream, odb, strlen(buffer), GIT_OBJ_TAG) < 0) - return -1; + if ((error = git_odb_open_wstream( + &stream, odb, strlen(buffer), GIT_OBJ_TAG)) < 0) + return error; - git_odb_stream_write(stream, buffer, strlen(buffer)); + if (!(error = git_odb_stream_write(stream, buffer, strlen(buffer)))) + error = git_odb_stream_finalize_write(oid, stream); - error = git_odb_stream_finalize_write(oid, stream); git_odb_stream_free(stream); if (error < 0) { git_buf_free(&ref_name); - return -1; + return error; } - error = git_reference_create(&new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); + error = git_reference_create( + &new_ref, repo, ref_name.ptr, oid, allow_ref_overwrite, NULL, NULL); git_reference_free(new_ref); git_buf_free(&ref_name); From 2b52a0bfaedf7571e7ecd706947f5865d513760c Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Tue, 13 May 2014 16:32:27 -0700 Subject: [PATCH 22/95] Increase use of config snapshots And decrease extra reload checks of config data. --- src/attrcache.c | 11 +++++++---- src/config.h | 6 ++++++ src/config_cache.c | 42 ++++++++++++++++++++++++------------------ src/diff.c | 19 ++++++++++--------- src/refdb_fs.c | 18 +++++++----------- src/repository.c | 24 ++++++++++++------------ src/repository.h | 4 ++++ 7 files changed, 70 insertions(+), 54 deletions(-) diff --git a/src/attrcache.c b/src/attrcache.c index f1bc70467..56c028e60 100644 --- a/src/attrcache.c +++ b/src/attrcache.c @@ -349,14 +349,11 @@ int git_attr_cache__do_init(git_repository *repo) { int ret = 0; git_attr_cache *cache = git_repository_attr_cache(repo); - git_config *cfg; + git_config *cfg = NULL; if (cache) return 0; - if ((ret = git_repository_config__weakptr(&cfg, repo)) < 0) - return ret; - cache = git__calloc(1, sizeof(git_attr_cache)); GITERR_CHECK_ALLOC(cache); @@ -367,6 +364,9 @@ int git_attr_cache__do_init(git_repository *repo) return -1; } + if ((ret = git_repository_config_snapshot(&cfg, repo)) < 0) + goto cancel; + /* cache config settings for attributes and ignores */ ret = attr_cache__lookup_path( &cache->cfg_attr_file, cfg, GIT_ATTR_CONFIG, GIT_ATTR_FILE_XDG); @@ -390,11 +390,14 @@ int git_attr_cache__do_init(git_repository *repo) if (cache) goto cancel; /* raced with another thread, free this but no error */ + git_config_free(cfg); + /* insert default macros */ return git_attr_add_macro(repo, "binary", "-diff -crlf -text"); cancel: attr_cache__free(cache); + git_config_free(cfg); return ret; } diff --git a/src/config.h b/src/config.h index 00b6063e7..b0dcb49ac 100644 --- a/src/config.h +++ b/src/config.h @@ -76,4 +76,10 @@ extern int git_config__get_bool_force( extern int git_config__get_int_force( const git_config *cfg, const char *key, int fallback_value); +/* API for repository cvar-style lookups from config - not cached, but + * uses cvar value maps and fallbacks + */ +extern int git_config__cvar( + int *out, git_config *config, git_cvar_cached cvar); + #endif diff --git a/src/config_cache.c b/src/config_cache.c index 4bcbf02bf..dca9976f8 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -7,11 +7,11 @@ #include "common.h" #include "fileops.h" +#include "repository.h" #include "config.h" #include "git2/config.h" #include "vector.h" #include "filter.h" -#include "repository.h" struct map_data { const char *cvar_name; @@ -69,32 +69,38 @@ static struct map_data _cvar_maps[] = { {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, {"core.safecrlf", NULL, 0, GIT_SAFE_CRLF_DEFAULT}, + {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, }; +int git_config__cvar(int *out, git_config *config, git_cvar_cached cvar) +{ + int error = 0; + struct map_data *data = &_cvar_maps[(int)cvar]; + const git_config_entry *entry; + + git_config__lookup_entry(&entry, config, data->cvar_name, false); + + if (!entry) + *out = data->default_value; + else if (data->maps) + error = git_config_lookup_map_value( + out, data->maps, data->map_count, entry->value); + else + error = git_config_parse_bool(out, entry->value); + + return error; +} + int git_repository__cvar(int *out, git_repository *repo, git_cvar_cached cvar) { *out = repo->cvar_cache[(int)cvar]; if (*out == GIT_CVAR_NOT_CACHED) { - struct map_data *data = &_cvar_maps[(int)cvar]; - git_config *config; int error; - const git_config_entry *entry; + git_config *config; - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - git_config__lookup_entry(&entry, config, data->cvar_name, false); - - if (!entry) - *out = data->default_value; - else if (data->maps) - error = git_config_lookup_map_value( - out, data->maps, data->map_count, entry->value); - else - error = git_config_parse_bool(out, entry->value); - - if (error < 0) + if ((error = git_repository_config__weakptr(&config, repo)) < 0 || + (error = git_config__cvar(out, config, cvar)) < 0) return error; repo->cvar_cache[(int)cvar] = *out; diff --git a/src/diff.c b/src/diff.c index b3e36101c..325c599e0 100644 --- a/src/diff.c +++ b/src/diff.c @@ -381,7 +381,7 @@ static int diff_list_apply_options( git_diff *diff, const git_diff_options *opts) { - git_config *cfg; + git_config *cfg = NULL; git_repository *repo = diff->repo; git_pool *pool = &diff->pool; int val; @@ -406,20 +406,20 @@ static int diff_list_apply_options( diff->opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED; /* load config values that affect diff behavior */ - if ((val = git_repository_config__weakptr(&cfg, repo)) < 0) + if ((val = git_repository_config_snapshot(&cfg, repo)) < 0) return val; - if (!git_repository__cvar(&val, repo, GIT_CVAR_SYMLINKS) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_SYMLINKS) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_HAS_SYMLINKS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_IGNORESTAT) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_IGNORESTAT) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_IGNORE_STAT; if ((diff->opts.flags & GIT_DIFF_IGNORE_FILEMODE) == 0 && - !git_repository__cvar(&val, repo, GIT_CVAR_FILEMODE) && val) + !git_config__cvar(&val, cfg, GIT_CVAR_FILEMODE) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_MODE_BITS; - if (!git_repository__cvar(&val, repo, GIT_CVAR_TRUSTCTIME) && val) + if (!git_config__cvar(&val, cfg, GIT_CVAR_TRUSTCTIME) && val) diff->diffcaps = diff->diffcaps | GIT_DIFFCAPS_TRUST_CTIME; /* Don't set GIT_DIFFCAPS_USE_DEV - compile time option in core git */ @@ -481,8 +481,6 @@ static int diff_list_apply_options( /* strdup prefix from pool so we're not dependent on external data */ diff->opts.old_prefix = diff_strdup_prefix(pool, diff->opts.old_prefix); diff->opts.new_prefix = diff_strdup_prefix(pool, diff->opts.new_prefix); - if (!diff->opts.old_prefix || !diff->opts.new_prefix) - return -1; if (DIFF_FLAG_IS_SET(diff, GIT_DIFF_REVERSE)) { const char *tmp_prefix = diff->opts.old_prefix; @@ -490,7 +488,10 @@ static int diff_list_apply_options( diff->opts.new_prefix = tmp_prefix; } - return 0; + git_config_free(cfg); + + /* check strdup results for error */ + return (!diff->opts.old_prefix || !diff->opts.new_prefix) ? -1 : 0; } static void diff_list_free(git_diff *diff) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index f9bd4eab5..dd8bf7916 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -927,19 +927,15 @@ static int has_reflog(git_repository *repo, const char *name); /* We only write if it's under heads/, remotes/ or notes/ or if it already has a log */ static int should_write_reflog(int *write, git_repository *repo, const char *name) { - git_config *config; - int error, logall, is_bare; + int error, logall; + + error = git_repository__cvar(&logall, repo, GIT_CVAR_LOGALLREFUPDATES); + if (error < 0) + return error; /* Defaults to the opposite of the repo being bare */ - is_bare = git_repository_is_bare(repo); - logall = !is_bare; - - if ((error = git_repository_config__weakptr(&config, repo)) < 0) - return error; - - error = git_config_get_bool(&logall, config, "core.logallrefupdates"); - if (error < 0 && error != GIT_ENOTFOUND) - return error; + if (logall == GIT_LOGALLREFUPDATES_UNSET) + logall = !git_repository_is_bare(repo); if (!logall) { *write = 0; diff --git a/src/repository.c b/src/repository.c index 7d055e28e..b0db5484a 100644 --- a/src/repository.c +++ b/src/repository.c @@ -443,7 +443,6 @@ int git_repository_open_ext( int error; git_buf path = GIT_BUF_INIT, parent = GIT_BUF_INIT; git_repository *repo; - git_config *config; if (repo_ptr) *repo_ptr = NULL; @@ -458,23 +457,24 @@ int git_repository_open_ext( repo->path_repository = git_buf_detach(&path); GITERR_CHECK_ALLOC(repo->path_repository); - if ((error = git_repository_config_snapshot(&config, repo)) < 0) - return error; - if ((flags & GIT_REPOSITORY_OPEN_BARE) != 0) repo->is_bare = 1; - else if ((error = load_config_data(repo, config)) < 0 || - (error = load_workdir(repo, config, &parent)) < 0) - { + else { + git_config *config = NULL; + + if ((error = git_repository_config_snapshot(&config, repo)) < 0 || + (error = load_config_data(repo, config)) < 0 || + (error = load_workdir(repo, config, &parent)) < 0) + git_repository_free(repo); + git_config_free(config); - git_repository_free(repo); - return error; } - git_config_free(config); + if (!error) + *repo_ptr = repo; git_buf_free(&parent); - *repo_ptr = repo; - return 0; + + return error; } int git_repository_open(git_repository **repo_out, const char *path) diff --git a/src/repository.h b/src/repository.h index 27eec9dd8..aba16a016 100644 --- a/src/repository.h +++ b/src/repository.h @@ -39,6 +39,7 @@ typedef enum { GIT_CVAR_ABBREV, /* core.abbrev */ GIT_CVAR_PRECOMPOSE, /* core.precomposeunicode */ GIT_CVAR_SAFE_CRLF, /* core.safecrlf */ + GIT_CVAR_LOGALLREFUPDATES, /* core.logallrefupdates */ GIT_CVAR_CACHE_MAX } git_cvar_cached; @@ -92,6 +93,9 @@ typedef enum { GIT_PRECOMPOSE_DEFAULT = GIT_CVAR_FALSE, /* core.safecrlf */ GIT_SAFE_CRLF_DEFAULT = GIT_CVAR_FALSE, + /* core.logallrefupdates */ + GIT_LOGALLREFUPDATES_UNSET = 2, + GIT_LOGALLREFUPDATES_DEFAULT = GIT_LOGALLREFUPDATES_UNSET, } git_cvar_value; /* internal repository init flags */ From 4af0ef9690e9fdfc81afbeed7039d02a5f191001 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Thu, 15 May 2014 11:09:49 -0400 Subject: [PATCH 23/95] Fix mutex init/free in config_file.c --- src/config_file.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/config_file.c b/src/config_file.c index fdc9bc8a2..56271144b 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -270,7 +270,6 @@ static int config_open(git_config_backend *cfg, git_config_level_t level) if ((res = refcounted_strmap_alloc(&b->header.values)) < 0) return res; - git_mutex_init(&b->header.values_mutex); git_array_init(b->readers); reader = git_array_alloc(b->readers); if (!reader) { @@ -375,6 +374,7 @@ static void backend_free(git_config_backend *_backend) git__free(backend->file_path); refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -686,6 +686,7 @@ int git_config_file__ondisk(git_config_backend **out, const char *path) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->file_path = git__strdup(path); GITERR_CHECK_ALLOC(backend->file_path); @@ -758,6 +759,7 @@ static void backend_readonly_free(git_config_backend *_backend) return; refcounted_strmap_free(backend->header.values); + git_mutex_free(&backend->header.values_mutex); git__free(backend); } @@ -784,6 +786,7 @@ int git_config_file__snapshot(git_config_backend **out, diskfile_backend *in) GITERR_CHECK_ALLOC(backend); backend->header.parent.version = GIT_CONFIG_BACKEND_VERSION; + git_mutex_init(&backend->header.values_mutex); backend->snapshot_from = in; From 8487e23797cef0284b592b5ef93eaacbac7196dc Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Thu, 15 May 2014 10:56:28 -0700 Subject: [PATCH 24/95] Better search path sandboxing There are a number of tests that modify the global or system search paths during the tests. This adds a helper function to make it easier to restore those paths and makes sure that they are getting restored in a manner that preserves test isolation. --- tests/clar_libgit2.c | 13 +++++++++++++ tests/clar_libgit2.h | 2 ++ tests/config/global.c | 28 +--------------------------- tests/config/include.c | 2 ++ tests/core/env.c | 26 ++++++++++++-------------- tests/main.c | 6 +----- tests/repo/config.c | 38 ++++++++++++++++++++------------------ tests/repo/open.c | 5 +++-- 8 files changed, 54 insertions(+), 66 deletions(-) diff --git a/tests/clar_libgit2.c b/tests/clar_libgit2.c index b2730f4d1..0a4c3e8e5 100644 --- a/tests/clar_libgit2.c +++ b/tests/clar_libgit2.c @@ -518,3 +518,16 @@ void cl_fake_home(void) GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); git_buf_free(&path); } + +void cl_sandbox_set_search_path_defaults(void) +{ + const char *sandbox_path = clar_sandbox_path(); + + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path); + git_libgit2_opts( + GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_SYSTEM, sandbox_path); +} + diff --git a/tests/clar_libgit2.h b/tests/clar_libgit2.h index f84d9e353..da37bd655 100644 --- a/tests/clar_libgit2.h +++ b/tests/clar_libgit2.h @@ -139,4 +139,6 @@ void cl_repo_set_string(git_repository *repo, const char *cfg, const char *value void cl_fake_home(void); void cl_fake_home_cleanup(void *); +void cl_sandbox_set_search_path_defaults(void); + #endif diff --git a/tests/config/global.c b/tests/config/global.c index 006b34628..fc471f90d 100644 --- a/tests/config/global.c +++ b/tests/config/global.c @@ -2,25 +2,10 @@ #include "buffer.h" #include "fileops.h" -static git_config_level_t setting[3] = { - GIT_CONFIG_LEVEL_GLOBAL, - GIT_CONFIG_LEVEL_XDG, - GIT_CONFIG_LEVEL_SYSTEM -}; -static char *restore[3]; - void test_config_global__initialize(void) { - int i; git_buf path = GIT_BUF_INIT; - /* snapshot old settings to restore later */ - for (i = 0; i < 3; ++i) { - cl_git_pass( - git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, setting[i], &path)); - restore[i] = git_buf_detach(&path); - } - cl_git_pass(git_futils_mkdir_r("home", NULL, 0777)); cl_git_pass(git_path_prettify(&path, "home", NULL)); cl_git_pass(git_libgit2_opts( @@ -41,18 +26,7 @@ void test_config_global__initialize(void) void test_config_global__cleanup(void) { - int i; - - for (i = 0; i < 3; ++i) { - cl_git_pass( - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, setting[i], restore[i])); - git__free(restore[i]); - restore[i] = NULL; - } - - cl_git_pass(git_futils_rmdir_r("home", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("xdg", NULL, GIT_RMDIR_REMOVE_FILES)); - cl_git_pass(git_futils_rmdir_r("etc", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); } void test_config_global__open_global(void) diff --git a/tests/config/include.c b/tests/config/include.c index 535573808..58bc690ff 100644 --- a/tests/config/include.c +++ b/tests/config/include.c @@ -47,6 +47,8 @@ void test_config_include__homedir(void) cl_assert_equal_s(str, "huzzah"); git_config_free(cfg); + + cl_sandbox_set_search_path_defaults(); } void test_config_include__refresh(void) diff --git a/tests/core/env.c b/tests/core/env.c index df1d92a02..293b786db 100644 --- a/tests/core/env.c +++ b/tests/core/env.c @@ -40,12 +40,12 @@ void test_core_env__initialize(void) } } -static void reset_global_search_path(void) +static void set_global_search_path_from_env(void) { cl_git_pass(git_sysdir_set(GIT_SYSDIR_GLOBAL, NULL)); } -static void reset_system_search_path(void) +static void set_system_search_path_from_env(void) { cl_git_pass(git_sysdir_set(GIT_SYSDIR_SYSTEM, NULL)); } @@ -69,9 +69,7 @@ void test_core_env__cleanup(void) (void)p_rmdir(*val); } - /* reset search paths to default */ - reset_global_search_path(); - reset_system_search_path(); + cl_sandbox_set_search_path_defaults(); } static void setenv_and_check(const char *name, const char *value) @@ -124,12 +122,12 @@ void test_core_env__0(void) GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); setenv_and_check("HOME", path.ptr); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); cl_setenv("HOME", env_save[0]); - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); @@ -138,7 +136,7 @@ void test_core_env__0(void) setenv_and_check("HOMEDRIVE", NULL); setenv_and_check("HOMEPATH", NULL); setenv_and_check("USERPROFILE", path.ptr); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); @@ -148,7 +146,7 @@ void test_core_env__0(void) if (root >= 0) { setenv_and_check("USERPROFILE", NULL); - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&found, testfile)); @@ -158,7 +156,7 @@ void test_core_env__0(void) setenv_and_check("HOMEDRIVE", path.ptr); path.ptr[root] = old; setenv_and_check("HOMEPATH", &path.ptr[root]); - reset_global_search_path(); + set_global_search_path_from_env(); cl_git_pass(git_sysdir_find_global_file(&found, testfile)); } @@ -185,7 +183,7 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("HOMEPATH", "doesnotexist")); cl_git_pass(cl_setenv("USERPROFILE", "doesnotexist")); #endif - reset_global_search_path(); + set_global_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); @@ -195,8 +193,8 @@ void test_core_env__1(void) cl_git_pass(cl_setenv("HOMEPATH", NULL)); cl_git_pass(cl_setenv("USERPROFILE", NULL)); #endif - reset_global_search_path(); - reset_system_search_path(); + set_global_search_path_from_env(); + set_system_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_global_file(&path, "nonexistentfile")); @@ -206,7 +204,7 @@ void test_core_env__1(void) #ifdef GIT_WIN32 cl_git_pass(cl_setenv("PROGRAMFILES", NULL)); - reset_system_search_path(); + set_system_search_path_from_env(); cl_assert_equal_i( GIT_ENOTFOUND, git_sysdir_find_system_file(&path, "nonexistentfile")); diff --git a/tests/main.c b/tests/main.c index ffbbcbf48..3de4f9801 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,16 +6,12 @@ int __cdecl main(int argc, char *argv[]) int main(int argc, char *argv[]) #endif { - const char *sandbox_path; int res; clar_test_init(argc, argv); git_threads_init(); - - sandbox_path = clar_sandbox_path(); - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, sandbox_path); - git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_XDG, sandbox_path); + cl_sandbox_set_search_path_defaults(); /* Run the test suite */ res = clar_test_run(); diff --git a/tests/repo/config.c b/tests/repo/config.c index 2e7be37aa..93dedd576 100644 --- a/tests/repo/config.c +++ b/tests/repo/config.c @@ -8,7 +8,8 @@ static git_buf path = GIT_BUF_INIT; void test_repo_config__initialize(void) { cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); git_buf_clear(&path); @@ -18,15 +19,19 @@ void test_repo_config__initialize(void) void test_repo_config__cleanup(void) { - cl_git_pass(git_path_prettify(&path, "alternate", NULL)); - cl_git_pass(git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)); + cl_sandbox_set_search_path_defaults(); + git_buf_free(&path); + + cl_git_pass( + git_futils_rmdir_r("alternate", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("alternate")); cl_fixture_cleanup("empty_standard_repo"); + } -void test_repo_config__open_missing_global(void) +void test_repo_config__can_open_global_when_there_is_no_file(void) { git_repository *repo; git_config *config, *global; @@ -40,23 +45,23 @@ void test_repo_config__open_missing_global(void) cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); cl_git_pass(git_config_set_string(global, "test.set", "42")); git_config_free(global); git_config_free(config); git_repository_free(repo); - - git_sysdir_global_shutdown(); } -void test_repo_config__open_missing_global_with_separators(void) +void test_repo_config__can_open_missing_global_with_separators(void) { git_repository *repo; git_config *config, *global; - cl_git_pass(git_buf_printf(&path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); + cl_git_pass(git_buf_printf( + &path, "%c%s", GIT_PATH_LIST_SEPARATOR, "dummy")); cl_git_pass(git_libgit2_opts( GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL, path.ptr)); @@ -69,20 +74,19 @@ void test_repo_config__open_missing_global_with_separators(void) cl_git_pass(git_repository_open(&repo, "empty_standard_repo")); cl_git_pass(git_repository_config(&config, repo)); - cl_git_pass(git_config_open_level(&global, config, GIT_CONFIG_LEVEL_GLOBAL)); + cl_git_pass(git_config_open_level( + &global, config, GIT_CONFIG_LEVEL_GLOBAL)); cl_git_pass(git_config_set_string(global, "test.set", "42")); git_config_free(global); git_config_free(config); git_repository_free(repo); - - git_sysdir_global_shutdown(); } #include "repository.h" -void test_repo_config__read_no_configs(void) +void test_repo_config__read_with_no_configs_at_all(void) { git_repository *repo; int val; @@ -106,9 +110,9 @@ void test_repo_config__read_no_configs(void) cl_assert_equal_i(GIT_ABBREV_DEFAULT, val); git_repository_free(repo); - git_sysdir_global_shutdown(); + /* with no local config, just system */ - /* with just system */ + cl_sandbox_set_search_path_defaults(); cl_must_pass(p_mkdir("alternate/1", 0777)); cl_git_pass(git_buf_joinpath(&path, path.ptr, "1")); @@ -123,7 +127,7 @@ void test_repo_config__read_no_configs(void) cl_assert_equal_i(10, val); git_repository_free(repo); - /* with xdg + system */ + /* with just xdg + system */ cl_must_pass(p_mkdir("alternate/2", 0777)); path.ptr[path.size - 1] = '2'; @@ -204,6 +208,4 @@ void test_repo_config__read_no_configs(void) cl_assert(!git_path_exists("empty_standard_repo/.git/config")); cl_assert(!git_path_exists("alternate/3/.gitconfig")); - - git_sysdir_global_shutdown(); } diff --git a/tests/repo/open.c b/tests/repo/open.c index 190adff1c..637c785d5 100644 --- a/tests/repo/open.c +++ b/tests/repo/open.c @@ -298,7 +298,8 @@ void test_repo_open__no_config(void) git_config *config; cl_fixture_sandbox("empty_standard_repo"); - cl_git_pass(cl_rename("empty_standard_repo/.gitted", "empty_standard_repo/.git")); + cl_git_pass(cl_rename( + "empty_standard_repo/.gitted", "empty_standard_repo/.git")); /* remove local config */ cl_git_pass(git_futils_rmdir_r( @@ -325,7 +326,7 @@ void test_repo_open__no_config(void) git_repository_free(repo); cl_fixture_cleanup("empty_standard_repo"); - git_sysdir_global_shutdown(); + cl_sandbox_set_search_path_defaults(); } void test_repo_open__force_bare(void) From 649214be4bd9d8239787e3af7e6d877955d09e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 15 May 2014 19:59:05 +0200 Subject: [PATCH 25/95] pack: init the cache on packfile alloc When running multithreaded, it is not enough to check for the offmap allocation. Move the call to cache_init() to packfile allocation so we can be sure it is always allocated free of races. This fixes #2355. --- src/pack.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pack.c b/src/pack.c index d93ee25f9..ace7abb58 100644 --- a/src/pack.c +++ b/src/pack.c @@ -83,16 +83,12 @@ static void cache_free(git_pack_cache *cache) } git_offmap_free(cache->entries); - git_mutex_free(&cache->lock); + cache->entries = NULL; } - - memset(cache, 0, sizeof(*cache)); } static int cache_init(git_pack_cache *cache) { - memset(cache, 0, sizeof(*cache)); - cache->entries = git_offmap_alloc(); GITERR_CHECK_ALLOC(cache->entries); @@ -534,9 +530,6 @@ static int pack_dependency_chain(git_dependency_chain *chain_out, size_t size, elem_pos; git_otype type; - if (!p->bases.entries && (cache_init(&p->bases) < 0)) - return -1; - elem_pos = 0; while (true) { struct pack_chain_elem *elem; @@ -985,6 +978,7 @@ void git_packfile_free(struct git_pack_file *p) git__free(p->bad_object_sha1); git_mutex_free(&p->lock); + git_mutex_free(&p->bases.lock); git__free(p); } @@ -1120,6 +1114,11 @@ int git_packfile_alloc(struct git_pack_file **pack_out, const char *path) return -1; } + if (cache_init(&p->bases) < 0) { + git__free(p); + return -1; + } + *pack_out = p; return 0; From ec8a949a58864272860a4838c6b3d862beda7076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 30 Apr 2014 09:20:03 +0200 Subject: [PATCH 26/95] remote: remove remote-tracking branches on delete When we delete a remote, we also need to go through its fetch refspecs and remove the references they create locally. --- src/remote.c | 58 ++++++++++++++++++++++++++++++++--- tests/network/remote/delete.c | 12 +++++--- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/remote.c b/src/remote.c index ca1099a7f..f55375398 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1813,6 +1813,53 @@ static int remove_branch_config_related_entries( return error; } +static int remove_refs(git_repository *repo, const char *glob) +{ + git_reference_iterator *iter; + const char *name; + int error; + + if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) + return error; + + while ((error = git_reference_next_name(&name, iter)) == 0) { + if ((error = git_reference_remove(repo, name)) < 0) + break; + } + git_reference_iterator_free(iter); + + if (error == GIT_ITEROVER) + error = 0; + + return error; +} + +static int remove_remote_tracking(git_repository *repo, const char *remote_name) +{ + git_remote *remote; + int error; + size_t i, count; + + /* we want to use what's on the config, regardless of changes to the instance in memory */ + if ((error = git_remote_load(&remote, repo, remote_name)) < 0) + return error; + + count = git_remote_refspec_count(remote); + for (i = 0; i < count; i++) { + const git_refspec *refspec = git_remote_get_refspec(remote, i); + + /* shouldn't ever actually happen */ + if (refspec == NULL) + continue; + + if ((error = remove_refs(repo, git_refspec_dst(refspec))) < 0) + break; + } + + git_remote_free(remote); + return error; +} + int git_remote_delete(git_remote *remote) { int error; @@ -1827,14 +1874,17 @@ int git_remote_delete(git_remote *remote) repo = git_remote_owner(remote); - if ((error = rename_remote_config_section( - repo, git_remote_name(remote), NULL)) < 0) - return error; - if ((error = remove_branch_config_related_entries(repo, git_remote_name(remote))) < 0) return error; + if ((error = remove_remote_tracking(repo, git_remote_name(remote))) < 0) + return error; + + if ((error = rename_remote_config_section( + repo, git_remote_name(remote), NULL)) < 0) + return error; + git_remote_free(remote); return 0; diff --git a/tests/network/remote/delete.c b/tests/network/remote/delete.c index 5bf944cda..db55b0768 100644 --- a/tests/network/remote/delete.c +++ b/tests/network/remote/delete.c @@ -27,14 +27,18 @@ void test_network_remote_delete__cannot_delete_an_anonymous_remote(void) cl_git_fail(git_remote_delete(remote)); git_remote_free(remote); + git_remote_free(_remote); } -void test_network_remote_delete__deleting_a_remote_removes_the_remote_tracking_references(void) +void test_network_remote_delete__remove_remote_tracking_branches(void) { - cl_assert(false); + git_reference *ref; + + cl_git_pass(git_remote_delete(_remote)); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, _repo, "refs/remotes/test/master")); } -void test_network_remote_delete__deleting_a_remote_removes_the_remote_configuration_settings(void) +void test_network_remote_delete__remove_remote_configuration_settings(void) { cl_assert(count_config_entries_match(_repo, "remote\\.test\\.+") > 0); @@ -43,7 +47,7 @@ void test_network_remote_delete__deleting_a_remote_removes_the_remote_configurat cl_assert_equal_i(0, count_config_entries_match(_repo, "remote\\.test\\.+")); } -void test_network_remote_delete__deleting_a_remote_removes_the_branch_remote_configuration_settings(void) +void test_network_remote_delete__remove_branch_upstream_configuration_settings(void) { assert_config_entry_existence(_repo, "branch.mergeless.remote", true); assert_config_entry_existence(_repo, "branch.master.remote", true); From bafaf790cd26e6dd827599f2d07d76e82f346e1a Mon Sep 17 00:00:00 2001 From: Edward Lee Date: Fri, 16 May 2014 08:09:20 -0400 Subject: [PATCH 27/95] Fixed permissions on template directories. --- src/repository.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/repository.c b/src/repository.c index b0db5484a..695351977 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1190,6 +1190,7 @@ static int repo_init_structure( bool external_tpl = ((opts->flags & GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE) != 0); mode_t dmode = pick_dir_mode(opts); + bool chmod = opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK; /* Hide the ".git" directory */ #ifdef GIT_WIN32 @@ -1230,10 +1231,17 @@ static int repo_init_structure( default_template = true; } - if (tdir) - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); + if (tdir) { + if (chmod) { + error = git_futils_cp_r(tdir, repo_dir, + GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | + GIT_CPDIR_SIMPLE_TO_MODE, dmode); + } else { + error = git_futils_cp_r(tdir, repo_dir, + GIT_CPDIR_COPY_SYMLINKS | + GIT_CPDIR_SIMPLE_TO_MODE, dmode); + } + } git_buf_free(&template_buf); git_config_free(cfg); @@ -1254,9 +1262,15 @@ static int repo_init_structure( * - only create files if no external template was specified */ for (tpl = repo_template; !error && tpl->path; ++tpl) { - if (!tpl->content) - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); + if (!tpl->content) { + if (chmod) { + error = git_futils_mkdir( + tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); + } else { + error = git_futils_mkdir( + tpl->path, repo_dir, dmode, GIT_MKDIR_PATH); + } + } else if (!external_tpl) { const char *content = tpl->content; From f0b820dd67ee6ac53bf6bebd84dfa5c709c4b499 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Fri, 16 May 2014 12:38:56 -0400 Subject: [PATCH 28/95] Win32: Supply _O_NOINHERIT when calling _wopen --- src/win32/posix_w32.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 0d070f6b5..73bf92572 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -19,6 +19,15 @@ # define FILE_NAME_NORMALIZED 0 #endif +/* Options which we always provide to _wopen. + * + * _O_BINARY - Raw access; no translation of CR or LF characters + * _O_NOINHERIT - Do not mark the created handle as inheritable by child processes. + * The Windows default is 'not inheritable', but the CRT's default (following + * POSIX convention) is 'inheritable'. We have no desire for our handles to be + * inheritable on Windows, so specify the flag to get default behavior back. */ +#define STANDARD_OPEN_FLAGS (_O_BINARY | _O_NOINHERIT) + /* GetFinalPathNameByHandleW signature */ typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD); @@ -317,7 +326,7 @@ int p_open(const char *path, int flags, ...) va_end(arg_list); } - return _wopen(buf, flags | _O_BINARY, mode); + return _wopen(buf, flags | STANDARD_OPEN_FLAGS, mode); } int p_creat(const char *path, mode_t mode) @@ -327,7 +336,7 @@ int p_creat(const char *path, mode_t mode) if (utf8_to_16_with_errno(buf, path) < 0) return -1; - return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, mode); + return _wopen(buf, _O_WRONLY | _O_CREAT | _O_TRUNC | STANDARD_OPEN_FLAGS, mode); } int p_getcwd(char *buffer_out, size_t size) From d0f00de4d8e2173a3132f0024e74f5049638ce2f Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 May 2014 11:08:19 -0700 Subject: [PATCH 29/95] Increase binary detection len to 8k --- src/blob.c | 3 ++- src/diff_driver.c | 6 +++++- src/filter.h | 4 ++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/blob.c b/src/blob.c index ab7dec67f..30d5b705b 100644 --- a/src/blob.c +++ b/src/blob.c @@ -334,7 +334,8 @@ int git_blob_is_binary(const git_blob *blob) assert(blob); content.ptr = blob->odb_object->buffer; - content.size = min(blob->odb_object->cached.size, 4000); + content.size = + min(blob->odb_object->cached.size, GIT_FILTER_BYTES_TO_CHECK_NUL); content.asize = 0; return git_buf_text_is_binary(&content); diff --git a/src/diff_driver.c b/src/diff_driver.c index dc8e79e25..c3c5f365b 100644 --- a/src/diff_driver.c +++ b/src/diff_driver.c @@ -397,7 +397,11 @@ void git_diff_driver_update_options( int git_diff_driver_content_is_binary( git_diff_driver *driver, const char *content, size_t content_len) { - const git_buf search = { (char *)content, 0, min(content_len, 4000) }; + git_buf search; + + search.ptr = (char *)content; + search.size = min(content_len, GIT_FILTER_BYTES_TO_CHECK_NUL); + search.asize = 0; GIT_UNUSED(driver); diff --git a/src/filter.h b/src/filter.h index d0ace0f9a..5a366108b 100644 --- a/src/filter.h +++ b/src/filter.h @@ -10,6 +10,10 @@ #include "common.h" #include "git2/filter.h" +/* Amount of file to examine for NUL byte when checking binary-ness */ +#define GIT_FILTER_BYTES_TO_CHECK_NUL 8000 + +/* Possible CRLF values */ typedef enum { GIT_CRLF_GUESS = -1, GIT_CRLF_BINARY = 0, From 8af4966db15ed35832235627e2d01068d4734dea Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 16 May 2014 16:30:58 -0700 Subject: [PATCH 30/95] Git binary check compat tests A variety of data patterns for diffs verified to match the behavior of binary detection with Git on the command line. --- tests/diff/workdir.c | 114 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index a6d48abc6..f82bb00e8 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -1580,3 +1580,117 @@ void test_diff_workdir__can_update_index(void) git_diff_free(diff); } + +#define STR7 "0123456" +#define STR8 "01234567" +#define STR40 STR8 STR8 STR8 STR8 STR8 +#define STR200 STR40 STR40 STR40 STR40 STR40 +#define STR999Z STR200 STR200 STR200 STR200 STR40 STR40 STR40 STR40 \ + STR8 STR8 STR8 STR8 STR7 "\0" +#define STR1000 STR200 STR200 STR200 STR200 STR200 +#define STR3999Z STR1000 STR1000 STR1000 STR999Z +#define STR4000 STR1000 STR1000 STR1000 STR1000 + +static void assert_delta_binary(git_diff *diff, size_t idx, int is_binary) +{ + git_patch *patch; + const git_diff_delta *delta; + + cl_git_pass(git_patch_from_diff(&patch, diff, idx)); + delta = git_patch_get_delta(patch); + cl_assert_equal_b((delta->flags & GIT_DIFF_FLAG_BINARY), is_binary); + git_patch_free(patch); +} + +void test_diff_workdir__binary_detection(void) +{ + git_index *idx; + git_diff *diff = NULL; + git_buf b = GIT_BUF_INIT; + int i; + git_buf data[10] = { + { "1234567890", 0, 0 }, /* 0 - all ascii text control */ + { "Åü†HøπΩ", 0, 0 }, /* 1 - UTF-8 multibyte text */ + { "\xEF\xBB\xBFÜ⤒ƒ8£€", 0, 0 }, /* 2 - UTF-8 with BOM */ + { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */ + { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */ + { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */ + { STR4000 STR4000 "\0", 0, 8001 }, /* 6 - ASCII with NUL at 8001 */ + { "\x00\xDC\x00\x6E\x21\x39\xFE\x0E\x00\x63\x00\xF8" + "\x00\x64\x00\x65\x20\x48", 0, 18 }, /* 7 - UTF-16 text */ + { "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d" + "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d", + 0, 26 }, /* 8 - All non-printable characters (no NUL) */ + { "Hello \x01\x02\x03\x04\x05\x06 World!\x01\x02\x03\x04" + "\x05\x06\x07", 0, 26 }, /* 9 - 50-50 non-printable (no NUL) */ + }; + + g_repo = cl_git_sandbox_init("empty_standard_repo"); + cl_git_pass(git_repository_index(&idx, g_repo)); + + /* We start with ASCII in index and test data in workdir, + * then we will try with test data in index and ASCII in workdir. + */ + + cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_mkfile(b.ptr, "baseline"); + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + if (data[i].size == 0) + data[i].size = strlen(data[i].ptr); + cl_git_write2file( + b.ptr, data[i].ptr, data[i].size, O_WRONLY|O_TRUNC, 0664); + } + git_index_write(idx); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + /* The above have been checked to match command-line Git */ + + git_diff_free(diff); + + cl_git_pass(git_buf_sets(&b, "empty_standard_repo/0")); + for (i = 0; i < 10; ++i) { + b.ptr[b.size - 1] = '0' + i; + cl_git_pass(git_index_add_bypath(idx, &b.ptr[b.size - 1])); + + cl_git_write2file(b.ptr, "baseline\n", 9, O_WRONLY|O_TRUNC, 0664); + } + git_index_write(idx); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, NULL)); + + cl_assert_equal_i(10, git_diff_num_deltas(diff)); + + /* using diff binary detection (i.e. looking for NUL byte) */ + assert_delta_binary(diff, 0, false); + assert_delta_binary(diff, 1, false); + assert_delta_binary(diff, 2, false); + assert_delta_binary(diff, 3, true); + assert_delta_binary(diff, 4, true); + assert_delta_binary(diff, 5, true); + assert_delta_binary(diff, 6, false); + assert_delta_binary(diff, 7, true); + assert_delta_binary(diff, 8, false); + assert_delta_binary(diff, 9, false); + + git_diff_free(diff); + + git_index_free(idx); + git_buf_free(&b); +} From f7310540ae888454f9ab69200cfcd8df07faf957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 13 May 2014 02:41:48 +0200 Subject: [PATCH 31/95] indexer: use mmap for writing Some OSs cannot keep their ideas about file content straight when mixing standard IO with file mapping. As we use mmap for reading from the packfile, let's make writing to the pack file use mmap. --- src/indexer.c | 156 ++++++++++++++++++++++++++---------------------- src/map.h | 1 + src/posix.c | 7 +++ src/posix.h | 1 + src/unix/map.c | 6 ++ src/win32/map.c | 5 ++ 6 files changed, 103 insertions(+), 73 deletions(-) diff --git a/src/indexer.c b/src/indexer.c index 68496ceea..11268e018 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -34,7 +34,6 @@ struct git_indexer { have_delta :1; struct git_pack_header hdr; struct git_pack_file *pack; - git_filebuf pack_file; unsigned int mode; git_off_t off; git_off_t entry_start; @@ -67,33 +66,18 @@ const git_oid *git_indexer_hash(const git_indexer *idx) return &idx->hash; } -static int open_pack(struct git_pack_file **out, const char *filename) -{ - struct git_pack_file *pack; - - if (git_packfile_alloc(&pack, filename) < 0) - return -1; - - if ((pack->mwf.fd = p_open(pack->pack_name, O_RDONLY)) < 0) { - giterr_set(GITERR_OS, "Failed to open packfile."); - git_packfile_free(pack); - return -1; - } - - *out = pack; - return 0; -} - static int parse_header(struct git_pack_header *hdr, struct git_pack_file *pack) { int error; + git_map map; + + if ((error = p_mmap(&map, sizeof(*hdr), GIT_PROT_READ, GIT_MAP_SHARED, pack->mwf.fd, 0)) < 0) + return error; + + memcpy(hdr, map.data, sizeof(*hdr)); + p_munmap(&map); /* Verify we recognize this pack file format. */ - if ((error = p_read(pack->mwf.fd, hdr, sizeof(*hdr))) < 0) { - giterr_set(GITERR_OS, "Failed to read in pack header"); - return error; - } - if (hdr->hdr_signature != ntohl(PACK_SIGNATURE)) { giterr_set(GITERR_INDEXER, "Wrong pack signature"); return -1; @@ -124,9 +108,9 @@ int git_indexer_new( void *progress_payload) { git_indexer *idx; - git_buf path = GIT_BUF_INIT; + git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT; static const char suff[] = "/pack"; - int error; + int error, fd; idx = git__calloc(1, sizeof(git_indexer)); GITERR_CHECK_ALLOC(idx); @@ -140,19 +124,30 @@ int git_indexer_new( if (error < 0) goto cleanup; - error = git_filebuf_open(&idx->pack_file, path.ptr, - GIT_FILEBUF_TEMPORARY | GIT_FILEBUF_DO_NOT_BUFFER, - idx->mode); + fd = git_futils_mktmp(&tmp_path, git_buf_cstr(&path), idx->mode); git_buf_free(&path); + if (fd < 0) + goto cleanup; + + error = git_packfile_alloc(&idx->pack, git_buf_cstr(&tmp_path)); + git_buf_free(&tmp_path); + if (error < 0) goto cleanup; + idx->pack->mwf.fd = fd; + if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) + goto cleanup; + *out = idx; return 0; cleanup: + if (fd != -1) + p_close(fd); + git_buf_free(&path); - git_filebuf_cleanup(&idx->pack_file); + git_buf_free(&tmp_path); git__free(idx); return -1; } @@ -429,6 +424,40 @@ static void hash_partially(git_indexer *idx, const uint8_t *data, size_t size) idx->inbuf_len += size - to_expell; } +static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t size) +{ + git_file fd = idx->pack->mwf.fd; + long page_size = git__page_size(); + git_off_t page_start, page_offset; + git_map map; + int error; + + /* the offset needs to be at the beginning of the a page boundary */ + page_start = (offset / page_size) * page_size; + page_offset = offset - page_start; + + if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) + return error; + + memcpy(map.data + page_offset, data, size); + p_munmap(&map); + + return 0; +} + +static int append_to_pack(git_indexer *idx, const void *data, size_t size) +{ + git_off_t current_size = idx->pack->mwf.size; + + /* add the extra space we need at the end */ + if (p_ftruncate(idx->pack->mwf.fd, current_size + size) < 0) { + giterr_system_set(errno); + return -1; + } + + return write_at(idx, data, idx->pack->mwf.size, size); +} + int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_transfer_progress *stats) { int error = -1; @@ -440,22 +469,13 @@ int git_indexer_append(git_indexer *idx, const void *data, size_t size, git_tran processed = stats->indexed_objects; - if ((error = git_filebuf_write(&idx->pack_file, data, size)) < 0) + if ((error = append_to_pack(idx, data, size)) < 0) return error; hash_partially(idx, data, (int)size); /* Make sure we set the new size of the pack */ - if (idx->opened_pack) { - idx->pack->mwf.size += size; - } else { - if ((error = open_pack(&idx->pack, idx->pack_file.path_lock)) < 0) - return error; - idx->opened_pack = 1; - mwf = &idx->pack->mwf; - if ((error = git_mwindow_file_register(&idx->pack->mwf)) < 0) - return error; - } + idx->pack->mwf.size += size; if (!idx->parsed_header) { unsigned int total_objects; @@ -616,17 +636,10 @@ static int index_path(git_buf *path, git_indexer *idx, const char *suffix) * Rewind the packfile by the trailer, as we might need to fix the * packfile by injecting objects at the tail and must overwrite it. */ -static git_off_t seek_back_trailer(git_indexer *idx) +static void seek_back_trailer(git_indexer *idx) { - git_off_t off; - - if ((off = p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_CUR)) < 0) - return -1; - idx->pack->mwf.size -= GIT_OID_RAWSZ; git_mwindow_free_all(&idx->pack->mwf); - - return off; } static int inject_object(git_indexer *idx, git_oid *id) @@ -642,7 +655,8 @@ static int inject_object(git_indexer *idx, git_oid *id) size_t len, hdr_len; int error; - entry_start = seek_back_trailer(idx); + seek_back_trailer(idx); + entry_start = idx->pack->mwf.size; if (git_odb_read(&obj, idx->odb, id) < 0) return -1; @@ -657,7 +671,9 @@ static int inject_object(git_indexer *idx, git_oid *id) /* Write out the object header */ hdr_len = git_packfile__object_header(hdr, len, git_odb_object_type(obj)); - git_filebuf_write(&idx->pack_file, hdr, hdr_len); + if ((error = append_to_pack(idx, hdr, hdr_len)) < 0) + goto cleanup; + idx->pack->mwf.size += hdr_len; entry->crc = crc32(entry->crc, hdr, (uInt)hdr_len); @@ -665,13 +681,16 @@ static int inject_object(git_indexer *idx, git_oid *id) goto cleanup; /* And then the compressed object */ - git_filebuf_write(&idx->pack_file, buf.ptr, buf.size); + if ((error = append_to_pack(idx, buf.ptr, buf.size)) < 0) + goto cleanup; + idx->pack->mwf.size += buf.size; entry->crc = htonl(crc32(entry->crc, (unsigned char *)buf.ptr, (uInt)buf.size)); git_buf_free(&buf); /* Write a fake trailer so the pack functions play ball */ - if ((error = git_filebuf_write(&idx->pack_file, &foo, GIT_OID_RAWSZ)) < 0) + + if ((error = append_to_pack(idx, &foo, GIT_OID_RAWSZ)) < 0) goto cleanup; idx->pack->mwf.size += GIT_OID_RAWSZ; @@ -817,19 +836,12 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta ctx = &idx->trailer; git_hash_ctx_init(ctx); - git_mwindow_free_all(mwf); + /* Update the header to include the numer of local objects we injected */ idx->hdr.hdr_entries = htonl(stats->total_objects + stats->local_objects); - if (p_lseek(idx->pack_file.fd, 0, SEEK_SET) < 0) { - giterr_set(GITERR_OS, "failed to seek to the beginning of the pack"); + if (write_at(idx, &idx->hdr, 0, sizeof(struct git_pack_header)) < 0) return -1; - } - - if (p_write(idx->pack_file.fd, &idx->hdr, sizeof(struct git_pack_header)) < 0) { - giterr_set(GITERR_OS, "failed to update the pack header"); - return -1; - } /* * We now use the same technique as before to determine the @@ -837,6 +849,7 @@ static int update_header_and_rehash(git_indexer *idx, git_transfer_progress *sta * hash_partially() keep the existing trailer out of the * calculation. */ + git_mwindow_free_all(mwf); idx->inbuf_len = 0; while (hashed < mwf->size) { ptr = git_mwindow_open(mwf, &w, hashed, chunk, &left); @@ -906,13 +919,7 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) return -1; git_hash_final(&trailer_hash, &idx->trailer); - if (p_lseek(idx->pack_file.fd, -GIT_OID_RAWSZ, SEEK_END) < 0) - return -1; - - if (p_write(idx->pack_file.fd, &trailer_hash, GIT_OID_RAWSZ) < 0) { - giterr_set(GITERR_OS, "failed to update pack trailer"); - return -1; - } + write_at(idx, &trailer_hash, idx->pack->mwf.size - GIT_OID_RAWSZ, GIT_OID_RAWSZ); } git_vector_sort(&idx->objects); @@ -995,14 +1002,18 @@ int git_indexer_commit(git_indexer *idx, git_transfer_progress *stats) git_mwindow_free_all(&idx->pack->mwf); /* We need to close the descriptor here so Windows doesn't choke on commit_at */ - p_close(idx->pack->mwf.fd); + if (p_close(idx->pack->mwf.fd) < 0) { + giterr_set(GITERR_OS, "failed to close packfile"); + goto on_error; + } + idx->pack->mwf.fd = -1; if (index_path(&filename, idx, ".pack") < 0) goto on_error; + /* And don't forget to rename the packfile to its new place. */ - if (git_filebuf_commit_at(&idx->pack_file, filename.ptr) < 0) - return -1; + p_rename(idx->pack->pack_name, git_buf_cstr(&filename)); git_buf_free(&filename); return 0; @@ -1022,7 +1033,7 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->objects); - if (idx->pack) { + if (idx->pack && idx->pack->idx_cache) { struct git_pack_entry *pentry; kh_foreach_value( idx->pack->idx_cache, pentry, { git__free(pentry); }); @@ -1032,6 +1043,5 @@ void git_indexer_free(git_indexer *idx) git_vector_free_deep(&idx->deltas); git_packfile_free(idx->pack); - git_filebuf_cleanup(&idx->pack_file); git__free(idx); } diff --git a/src/map.h b/src/map.h index da3d1e19a..722eb7a30 100644 --- a/src/map.h +++ b/src/map.h @@ -42,5 +42,6 @@ typedef struct { /* memory mapped buffer */ extern int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset); extern int p_munmap(git_map *map); +extern long git__page_size(void); #endif /* INCLUDE_map_h__ */ diff --git a/src/posix.c b/src/posix.c index 7484ac0d8..7aeb0e6c1 100644 --- a/src/posix.c +++ b/src/posix.c @@ -207,6 +207,13 @@ int p_write(git_file fd, const void *buf, size_t cnt) #include "map.h" +long git__page_size(void) +{ + /* dummy; here we don't need any alignment anyway */ + return 4096; +} + + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { GIT_MMAP_VALIDATE(out, len, prot, flags); diff --git a/src/posix.h b/src/posix.h index f85b1aebd..745e4af75 100644 --- a/src/posix.h +++ b/src/posix.h @@ -60,6 +60,7 @@ extern int p_write(git_file fd, const void *buf, size_t cnt); #define p_lseek(f,n,w) lseek(f, n, w) #define p_close(fd) close(fd) #define p_umask(m) umask(m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) extern int p_open(const char *path, int flags, ...); extern int p_creat(const char *path, mode_t mode); diff --git a/src/unix/map.c b/src/unix/map.c index e62ab3e76..3d0cbbaf8 100644 --- a/src/unix/map.c +++ b/src/unix/map.c @@ -10,8 +10,14 @@ #include "map.h" #include +#include #include +long git__page_size(void) +{ + return sysconf(_SC_PAGE_SIZE); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { int mprot = 0; diff --git a/src/win32/map.c b/src/win32/map.c index 902ea3994..ef83f882e 100644 --- a/src/win32/map.c +++ b/src/win32/map.c @@ -23,6 +23,11 @@ static DWORD get_page_size(void) return page_size; } +long git__page_size(void) +{ + return (long)get_page_size(); +} + int p_mmap(git_map *out, size_t len, int prot, int flags, int fd, git_off_t offset) { HANDLE fh = (HANDLE)_get_osfhandle(fd); From 0731a5b4db086eefac1a842e37526ef7bdbaa7de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 14 May 2014 19:12:48 +0200 Subject: [PATCH 32/95] indexer: mmap fixes for Windows Windows has its own ftruncate() called _chsize_s(). p_mkstemp() is changed to use p_open() so we can make sure we open for writing; the addition of exclusive create is a good thing to do regardless, as we want a temporary path for ourselves. Lastly, MSVC doesn't quite know how to add two numbers if one of them is a void pointer, so let's alias it to unsigned char.C --- src/indexer.c | 4 +++- src/posix.h | 2 +- src/win32/posix.h | 6 ++++++ src/win32/posix_w32.c | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/indexer.c b/src/indexer.c index 11268e018..ebfdffb47 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -429,6 +429,7 @@ static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t git_file fd = idx->pack->mwf.fd; long page_size = git__page_size(); git_off_t page_start, page_offset; + unsigned char *map_data; git_map map; int error; @@ -439,7 +440,8 @@ static int write_at(git_indexer *idx, const void *data, git_off_t offset, size_t if ((error = p_mmap(&map, page_offset + size, GIT_PROT_WRITE, GIT_MAP_SHARED, fd, page_start)) < 0) return error; - memcpy(map.data + page_offset, data, size); + map_data = (unsigned char *)map.data; + memcpy(map_data + page_offset, data, size); p_munmap(&map); return 0; diff --git a/src/posix.h b/src/posix.h index 745e4af75..965cd98d5 100644 --- a/src/posix.h +++ b/src/posix.h @@ -60,7 +60,6 @@ extern int p_write(git_file fd, const void *buf, size_t cnt); #define p_lseek(f,n,w) lseek(f, n, w) #define p_close(fd) close(fd) #define p_umask(m) umask(m) -#define p_ftruncate(fd, sz) ftruncate(fd, sz) extern int p_open(const char *path, int flags, ...); extern int p_creat(const char *path, mode_t mode); @@ -74,6 +73,7 @@ extern int p_rename(const char *from, const char *to); #define p_rmdir(p) rmdir(p) #define p_chmod(p,m) chmod(p, m) #define p_access(p,m) access(p,m) +#define p_ftruncate(fd, sz) ftruncate(fd, sz) #define p_recv(s,b,l,f) recv(s,b,l,f) #define p_send(s,b,l,f) send(s,b,l,f) typedef int GIT_SOCKET; diff --git a/src/win32/posix.h b/src/win32/posix.h index 7f9d57cc3..2cbea1807 100644 --- a/src/win32/posix.h +++ b/src/win32/posix.h @@ -19,6 +19,12 @@ # define EAFNOSUPPORT (INT_MAX-1) #endif +#ifdef _MSC_VER +# define p_ftruncate(fd, sz) _chsize_s(fd, sz) +#else /* MinGW */ +# define p_ftruncate(fd, sz) _chsize(fd, sz) +#endif + GIT_INLINE(int) p_link(const char *old, const char *new) { GIT_UNUSED(old); diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 73bf92572..34938431a 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -578,7 +578,7 @@ int p_mkstemp(char *tmp_path) return -1; #endif - return p_creat(tmp_path, 0744); //-V536 + return p_open(tmp_path, O_RDWR | O_CREAT | O_EXCL, 0744); //-V536 } int p_access(const char* path, mode_t mode) From c6320bec93ec885208e602f7ebef3e7d79ca7901 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Sat, 17 May 2014 12:19:32 -0400 Subject: [PATCH 33/95] print_binary_hunk: Treat types with respect --- src/diff_print.c | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index 08e1e7f90..de1477a85 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -286,7 +286,9 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) { git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL; const void *old_data, *new_data; - size_t old_data_len, new_data_len, delta_data_len, inflated_len, remain; + git_off_t off_t_old_data_len, off_t_new_data_len; + unsigned long old_data_len, new_data_len, delta_data_len, inflated_len; + size_t remain; const char *out_type = "literal"; char *ptr; int error; @@ -294,8 +296,17 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) old_data = old ? git_blob_rawcontent(old) : NULL; new_data = new ? git_blob_rawcontent(new) : NULL; - old_data_len = old ? (size_t)git_blob_rawsize(old) : 0; - new_data_len = new ? (size_t)git_blob_rawsize(new) : 0; + off_t_old_data_len = old ? git_blob_rawsize(old) : 0; + off_t_new_data_len = new ? git_blob_rawsize(new) : 0; + + /* The git_delta function accepts unsigned long only */ + if (off_t_old_data_len > ULONG_MAX || off_t_new_data_len > ULONG_MAX) { + error = -1; + goto done; + } + + old_data_len = (unsigned long)off_t_old_data_len; + new_data_len = (unsigned long)off_t_new_data_len; out = &deflate; inflated_len = new_data_len; @@ -304,11 +315,17 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) &deflate, new_data, new_data_len)) < 0) goto done; + /* The git_delta function accepts unsigned long only */ + if (deflate.size > ULONG_MAX) { + error = -1; + goto done; + } + if (old && new) { void *delta_data; delta_data = git_delta(old_data, old_data_len, new_data, - new_data_len, &delta_data_len, deflate.size); + new_data_len, &delta_data_len, (unsigned long)deflate.size); if (delta_data) { error = git_zstream_deflatebuf(&delta, delta_data, delta_data_len); @@ -325,16 +342,16 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) } } - git_buf_printf(pi->buf, "%s %" PRIuZ "\n", out_type, inflated_len); + git_buf_printf(pi->buf, "%s %u\n", out_type, inflated_len); pi->line.num_lines++; for (ptr = out->ptr, remain = out->size; remain > 0; ) { size_t chunk_len = (52 < remain) ? 52 : remain; if (chunk_len <= 26) - git_buf_putc(pi->buf, chunk_len + 'A' - 1); + git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1); else - git_buf_putc(pi->buf, chunk_len - 26 + 'a' - 1); + git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); git_buf_put_base85(pi->buf, ptr, chunk_len); git_buf_putc(pi->buf, '\n'); From 4c9ffdff76d6d0c4dd65793148d38b35b7703d0d Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Sat, 17 May 2014 12:45:34 -0400 Subject: [PATCH 34/95] Fix printf format string from previous commit --- src/diff_print.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diff_print.c b/src/diff_print.c index de1477a85..26753515b 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -342,7 +342,7 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) } } - git_buf_printf(pi->buf, "%s %u\n", out_type, inflated_len); + git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len); pi->line.num_lines++; for (ptr = out->ptr, remain = out->size; remain > 0; ) { From d7a294633dc6420d2411500bd40c7dfd2aa76d37 Mon Sep 17 00:00:00 2001 From: Philip Kelley Date: Sat, 17 May 2014 16:58:09 -0400 Subject: [PATCH 35/95] Fix a bug in the pack::packbuilder suite --- tests/pack/packbuilder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/pack/packbuilder.c b/tests/pack/packbuilder.c index 53db81828..1059424ed 100644 --- a/tests/pack/packbuilder.c +++ b/tests/pack/packbuilder.c @@ -17,6 +17,7 @@ static git_transfer_progress _stats; void test_pack_packbuilder__initialize(void) { _repo = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(p_chdir("testrepo.git")); cl_git_pass(git_revwalk_new(&_revwalker, _repo)); cl_git_pass(git_packbuilder_new(&_packbuilder, _repo)); cl_git_pass(git_vector_init(&_commits, 0, NULL)); @@ -46,6 +47,7 @@ void test_pack_packbuilder__cleanup(void) git_indexer_free(_indexer); _indexer = NULL; + p_chdir(".."); cl_git_sandbox_cleanup(); _repo = NULL; } From 49e369b29d5bde227b162dd4681ecccff2f443df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 18 May 2014 10:06:49 +0200 Subject: [PATCH 36/95] message: don't assume the comment char The comment char is configurable and we need to provide a way for the user to specify which comment char they chose for their message. --- include/git2/message.h | 8 +++++--- src/message.c | 4 ++-- tests/object/commit/commitstagedfile.c | 2 +- tests/object/message.c | 14 +++++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/git2/message.h b/include/git2/message.h index bcdb72f6a..d78b1dce5 100644 --- a/include/git2/message.h +++ b/include/git2/message.h @@ -29,12 +29,14 @@ GIT_BEGIN_DECL * * @param message The message to be prettified. * - * @param strip_comments Non-zero to remove lines starting with "#", 0 to - * leave them in. + * @param strip_comments Non-zero to remove comment lines, 0 to leave them in. + * + * @param comment_char Comment character. Lines starting with this character + * are considered to be comments and removed if `strip_comments` is non-zero. * * @return 0 or an error code. */ -GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments); +GIT_EXTERN(int) git_message_prettify(git_buf *out, const char *message, int strip_comments, char comment_char); /** @} */ GIT_END_DECL diff --git a/src/message.c b/src/message.c index 07b2569ad..6c5a2379f 100644 --- a/src/message.c +++ b/src/message.c @@ -21,7 +21,7 @@ static size_t line_length_without_trailing_spaces(const char *line, size_t len) /* Greatly inspired from git.git "stripspace" */ /* see https://github.com/git/git/blob/497215d8811ac7b8955693ceaad0899ecd894ed2/builtin/stripspace.c#L4-67 */ -int git_message_prettify(git_buf *message_out, const char *message, int strip_comments) +int git_message_prettify(git_buf *message_out, const char *message, int strip_comments, char comment_char) { const size_t message_len = strlen(message); @@ -40,7 +40,7 @@ int git_message_prettify(git_buf *message_out, const char *message, int strip_co line_length = message_len - i; } - if (strip_comments && line_length && message[i] == '#') + if (strip_comments && line_length && message[i] == comment_char) continue; rtrimmed_line_length = line_length_without_trailing_spaces(message + i, line_length); diff --git a/tests/object/commit/commitstagedfile.c b/tests/object/commit/commitstagedfile.c index 9758ea9a2..5b48519b8 100644 --- a/tests/object/commit/commitstagedfile.c +++ b/tests/object/commit/commitstagedfile.c @@ -112,7 +112,7 @@ void test_object_commit_commitstagedfile__generate_predictable_object_ids(void) cl_git_pass(git_tree_lookup(&tree, repo, &tree_oid)); memset(&buffer, 0, sizeof(git_buf)); - cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0)); + cl_git_pass(git_message_prettify(&buffer, "Initial commit", 0, '#')); cl_git_pass(git_commit_create_v( &commit_oid, diff --git a/tests/object/message.c b/tests/object/message.c index ab5565341..40d8e7297 100644 --- a/tests/object/message.c +++ b/tests/object/message.c @@ -6,7 +6,7 @@ static void assert_message_prettifying(char *expected_output, char *input, int s { git_buf prettified_message = GIT_BUF_INIT; - git_message_prettify(&prettified_message, input, strip_comments); + git_message_prettify(&prettified_message, input, strip_comments, '#'); cl_assert_equal_s(expected_output, git_buf_cstr(&prettified_message)); git_buf_free(&prettified_message); @@ -175,25 +175,25 @@ void test_object_message__message_prettify(void) git_buf buffer; memset(&buffer, 0, sizeof(buffer)); - cl_git_pass(git_message_prettify(&buffer, "", 0)); + cl_git_pass(git_message_prettify(&buffer, "", 0, '#')); cl_assert_equal_s(buffer.ptr, ""); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "", 1)); + cl_git_pass(git_message_prettify(&buffer, "", 1, '#')); cl_assert_equal_s(buffer.ptr, ""); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "Short", 0)); + cl_git_pass(git_message_prettify(&buffer, "Short", 0, '#')); cl_assert_equal_s("Short\n", buffer.ptr); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "Short", 1)); + cl_git_pass(git_message_prettify(&buffer, "Short", 1, '#')); cl_assert_equal_s("Short\n", buffer.ptr); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0)); + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 0, '#')); cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n# with some comments still in\n"); git_buf_free(&buffer); - cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1)); + cl_git_pass(git_message_prettify(&buffer, "This is longer\nAnd multiline\n# with some comments still in\n", 1, '#')); cl_assert_equal_s(buffer.ptr, "This is longer\nAnd multiline\n"); git_buf_free(&buffer); } From 9c4feef9f8e4c3b57d164f62b1e5666e87c1d8f2 Mon Sep 17 00:00:00 2001 From: Albert Meltzer Date: Sat, 17 May 2014 12:44:21 -0700 Subject: [PATCH 37/95] Fix warning on uninitialized variable. --- src/indexer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/indexer.c b/src/indexer.c index ebfdffb47..25c3d0537 100644 --- a/src/indexer.c +++ b/src/indexer.c @@ -110,7 +110,7 @@ int git_indexer_new( git_indexer *idx; git_buf path = GIT_BUF_INIT, tmp_path = GIT_BUF_INIT; static const char suff[] = "/pack"; - int error, fd; + int error, fd = -1; idx = git__calloc(1, sizeof(git_indexer)); GITERR_CHECK_ALLOC(idx); From b2067248632e8bf88f8be40a18079fab95b68f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 15 May 2014 09:03:30 +0200 Subject: [PATCH 38/95] clone: add failing test for a mirror-clone with clone_into Show a failure to perform a mirror-clone from a repository, both local and remote. --- tests/network/fetchlocal.c | 26 ++++++++++++++++++++++++++ tests/online/clone.c | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 4c39394bb..0d23bef48 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -86,3 +86,29 @@ void test_network_fetchlocal__partial(void) git_strarray_free(&refnames); git_remote_free(origin); } + +void test_network_fetchlocal__clone_into_mirror(void) +{ + git_buf path = GIT_BUF_INIT; + git_repository *repo; + git_remote *remote; + git_reference *head; + + cl_git_pass(git_repository_init(&repo, "./foo.git", true)); + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_git_fixture_url("testrepo.git"))); + + git_remote_clear_refspecs(remote); + cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*")); + + cl_git_pass(git_clone_into(repo, remote, NULL, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&head, repo, "HEAD")); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + git_remote_free(remote); + git_reference_free(head); + git_repository_free(repo); + git_buf_free(&path); + cl_fixture_cleanup("./foo.git"); +} diff --git a/tests/online/clone.c b/tests/online/clone.c index 6e0e63950..e269771c0 100644 --- a/tests/online/clone.c +++ b/tests/online/clone.c @@ -164,6 +164,39 @@ void test_online_clone__clone_into(void) git_buf_free(&path); } +void test_online_clone__clone_mirror(void) +{ + git_buf path = GIT_BUF_INIT; + git_remote *remote; + git_reference *head; + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + bool fetch_progress_cb_was_called = false; + + cl_git_pass(git_repository_init(&g_repo, "./foo.git", true)); + cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL)); + + callbacks.transfer_progress = &fetch_progress; + callbacks.payload = &fetch_progress_cb_was_called; + git_remote_set_callbacks(remote, &callbacks); + + git_remote_clear_refspecs(remote); + cl_git_pass(git_remote_add_fetch(remote, "+refs/*:refs/*")); + + cl_git_pass(git_clone_into(g_repo, remote, NULL, NULL, NULL)); + + cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD")); + cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head)); + cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head)); + + cl_assert_equal_i(true, fetch_progress_cb_was_called); + + git_remote_free(remote); + git_reference_free(head); + git_buf_free(&path); + cl_fixture_cleanup("./foo.git"); +} + static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload) { int *callcount = (int*)payload; From 3c607685da2cf9a22559f53fd7670e1c4cad45e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 May 2014 13:36:00 +0200 Subject: [PATCH 39/95] clone: duplicate the remote Instead of changing the user-provided remote, duplicate it so we can add the extra refspec without having to worry about unsetting it before returning. --- src/clone.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/clone.c b/src/clone.c index c6be00f0e..c627edbc0 100644 --- a/src/clone.c +++ b/src/clone.c @@ -336,25 +336,30 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { int error = 0, old_fetchhead; - git_strarray refspecs; git_buf reflog_message = GIT_BUF_INIT; + git_remote *remote; + const git_remote_callbacks *callbacks; - assert(repo && remote); + assert(repo && _remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); return -1; } - - if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0) + if ((error = git_remote_dup(&remote, _remote)) < 0) return error; + callbacks = git_remote_get_callbacks(_remote); + if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && + (error = git_remote_set_callbacks(remote, git_remote_get_callbacks(_remote))) < 0) + goto cleanup; + if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) - return error; + goto cleanup; old_fetchhead = git_remote_update_fetchhead(remote); git_remote_set_update_fetchhead(remote, 0); @@ -375,15 +380,7 @@ int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_ cleanup: git_remote_set_update_fetchhead(remote, old_fetchhead); - - /* Go back to the original refspecs */ - { - int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs); - if (!error) - error = error_alt; - } - - git_strarray_free(&refspecs); + git_remote_free(remote); git_buf_free(&reflog_message); return error; From 32332fccc9f1e316bcecf3ab5133331315e31871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 May 2014 14:15:40 +0200 Subject: [PATCH 40/95] clone: don't error out if the branch already exists We set up the current branch after we fetch from the remote. This means that the user's refspec may have already created this reference. It is therefore not an error if we cannot create the branch because it already exists. This allows for the user to replicate git-clone's --mirror option. --- src/clone.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/clone.c b/src/clone.c index c627edbc0..24f1cae59 100644 --- a/src/clone.c +++ b/src/clone.c @@ -171,6 +171,10 @@ static int update_head_to_new_branch( git_reference_free(tracking_branch); + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + return error; } From 213a269a50479e0a2d762ae4c9ad1a4e52e53331 Mon Sep 17 00:00:00 2001 From: Martin Woodward Date: Mon, 19 May 2014 14:39:45 +0100 Subject: [PATCH 41/95] Restore attributions for fnmatch --- src/fnmatch.c | 34 ++++++++++++++++++++++++++++++++++ src/fnmatch.h | 27 ++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/src/fnmatch.c b/src/fnmatch.c index d8a83a8ed..d7899e3e6 100644 --- a/src/fnmatch.c +++ b/src/fnmatch.c @@ -5,6 +5,40 @@ * a Linking Exception. For full terms see the included COPYING file. */ +/* + * This file contains code originally derrived from OpenBSD fnmatch.c + * + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + /* * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. * Compares a filename or pathname to a pattern. diff --git a/src/fnmatch.h b/src/fnmatch.h index 920e7de4d..88af45939 100644 --- a/src/fnmatch.h +++ b/src/fnmatch.h @@ -1,8 +1,29 @@ /* - * Copyright (C) the libgit2 contributors. All rights reserved. + * Copyright (C) 2008 The Android Open Source Project + * 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. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. */ #ifndef INCLUDE_fnmatch__compat_h__ #define INCLUDE_fnmatch__compat_h__ From 16798d08cf3a1757deb9f0363b35fbf775cfc3fb Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 19 May 2014 14:57:09 -0700 Subject: [PATCH 42/95] Make core.safecrlf work on LF-ending platforms If you enabled core.safecrlf on an LF-ending platform, we would error even for files with all LFs. We should only be warning on irreversible mappings, I think. --- src/crlf.c | 4 +++- tests/filter/crlf.c | 6 +++--- tests/index/crlf.c | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/crlf.c b/src/crlf.c index dad3ecebc..22cba84ab 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -139,7 +139,9 @@ static int crlf_apply_to_odb( return GIT_PASSTHROUGH; /* If safecrlf is enabled, sanity-check the result. */ - if (stats.cr != stats.crlf || stats.lf != stats.crlf) { + if (stats.cr != stats.crlf || + (stats.crlf > 0 && stats.lf != stats.crlf)) { + switch (ca->safe_crlf) { case GIT_SAFE_CRLF_FAIL: giterr_set( diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c index 66c267e31..334b1e349 100644 --- a/tests/filter/crlf.c +++ b/tests/filter/crlf.c @@ -103,12 +103,12 @@ void test_filter_crlf__with_safecrlf(void) cl_git_fail(git_filter_list_apply_to_data(&out, fl, &in)); cl_assert_equal_i(giterr_last()->klass, GITERR_FILTER); - /* Normalized \n fails with safecrlf */ + /* Normalized \n is reversible, so does not fail with safecrlf */ in.ptr = "Normal\nLF\nonly\nline-endings.\n"; in.size = strlen(in.ptr); - cl_git_fail(git_filter_list_apply_to_data(&out, fl, &in)); - cl_assert_equal_i(giterr_last()->klass, GITERR_FILTER); + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s(in.ptr, out.ptr); git_filter_list_free(fl); git_buf_free(&out); diff --git a/tests/index/crlf.c b/tests/index/crlf.c index cf69c6226..7babd5939 100644 --- a/tests/index/crlf.c +++ b/tests/index/crlf.c @@ -134,3 +134,21 @@ void test_index_crlf__autocrlf_input_text_auto_attr(void) cl_git_pass(git_oid_fromstr(&oid, FILE_OID_LF)); cl_assert(git_oid_cmp(&oid, &entry->id) == 0); } + +void test_index_crlf__safecrlf_true_no_attrs(void) +{ + cl_repo_set_bool(g_repo, "core.autocrlf", true); + cl_repo_set_bool(g_repo, "core.safecrlf", true); + + cl_git_mkfile("crlf/newfile.txt", ALL_LF_TEXT_RAW); + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", ALL_CRLF_TEXT_RAW); + cl_git_pass(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_CRLF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); + + cl_git_mkfile("crlf/newfile.txt", MORE_LF_TEXT_RAW); + cl_git_fail(git_index_add_bypath(g_index, "newfile.txt")); +} From c094197bf92736bb1c40cf1ca87bda970ab7f999 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Mon, 19 May 2014 15:05:39 -0700 Subject: [PATCH 43/95] Just don't CRLF filter if there are no CRs --- src/crlf.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/crlf.c b/src/crlf.c index 22cba84ab..821e04eb2 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -138,10 +138,12 @@ static int crlf_apply_to_odb( if (git_buf_text_gather_stats(&stats, from, false)) return GIT_PASSTHROUGH; - /* If safecrlf is enabled, sanity-check the result. */ - if (stats.cr != stats.crlf || - (stats.crlf > 0 && stats.lf != stats.crlf)) { + /* If there are no CR characters to filter out, then just pass */ + if (!stats.cr) + return GIT_PASSTHROUGH; + /* If safecrlf is enabled, sanity-check the result. */ + if (stats.cr != stats.crlf || stats.lf != stats.crlf) { switch (ca->safe_crlf) { case GIT_SAFE_CRLF_FAIL: giterr_set( From ac11219b8014fec1c5a83f1b19cf78015adc856c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 19 May 2014 16:54:19 +0200 Subject: [PATCH 44/95] smart: send a flush when we disconnect The git server wants to hear a flush from us when we disconnect, particularly when we want to perform a fetch but are up to date. --- src/transports/smart.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/transports/smart.c b/src/transports/smart.c index 69eaf9b78..7fb41f788 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -272,6 +272,18 @@ static int git_smart__close(git_transport *transport) unsigned int i; git_pkt *p; int ret; + git_smart_subtransport_stream *stream; + const char flush[] = "0000"; + + /* + * If we're still connected at this point and not using RPC, + * we should say goodbye by sending a flush, or git-daemon + * will complain that we disconnected unexpectedly. + */ + if (t->connected && !t->rpc && + !t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) { + t->current_stream->write(t->current_stream, flush, 4); + } ret = git_smart__reset_stream(t, true); From 430866d28cca5e1c04ca5e65dc0f66ae00a2f288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 20 May 2014 08:29:51 +0200 Subject: [PATCH 45/95] Fix a leak in the tests --- tests/odb/foreach.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/odb/foreach.c b/tests/odb/foreach.c index ab3808b00..56daf7574 100644 --- a/tests/odb/foreach.c +++ b/tests/odb/foreach.c @@ -93,8 +93,8 @@ void test_odb_foreach__files_in_objects_dir(void) cl_git_pass(git_repository_open(&repo, "testrepo.git")); cl_git_pass(git_buf_printf(&buf, "%s/objects/somefile", git_repository_path(repo))); - cl_git_mkfile(buf.ptr, ""); + git_buf_free(&buf); cl_git_pass(git_repository_odb(&odb, repo)); cl_git_pass(git_odb_foreach(odb, foreach_cb, &nobj)); From 60cdf49583e235fd4441e7104a8b04d206824c7c Mon Sep 17 00:00:00 2001 From: Albert Meltzer Date: Mon, 19 May 2014 09:13:45 -0700 Subject: [PATCH 46/95] Minor fix for cmn/clone-into-mirror. A recently added check might skip initialization of old_fetchhead and go directly to cleanup. So, destruct in the opposite order of construction. --- src/clone.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/clone.c b/src/clone.c index 24f1cae59..9ac9eb2a1 100644 --- a/src/clone.c +++ b/src/clone.c @@ -342,7 +342,7 @@ static bool should_checkout( int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { - int error = 0, old_fetchhead; + int error; git_buf reflog_message = GIT_BUF_INIT; git_remote *remote; const git_remote_callbacks *callbacks; @@ -359,13 +359,12 @@ int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout callbacks = git_remote_get_callbacks(_remote); if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && - (error = git_remote_set_callbacks(remote, git_remote_get_callbacks(_remote))) < 0) + (error = git_remote_set_callbacks(remote, callbacks)) < 0) goto cleanup; if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) goto cleanup; - old_fetchhead = git_remote_update_fetchhead(remote); git_remote_set_update_fetchhead(remote, 0); git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); @@ -383,7 +382,6 @@ int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout error = git_checkout_head(repo, co_opts); cleanup: - git_remote_set_update_fetchhead(remote, old_fetchhead); git_remote_free(remote); git_buf_free(&reflog_message); From 8156835df17d89bd7ce1525792aa13146fab551e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 20 May 2014 09:29:39 +0200 Subject: [PATCH 47/95] smart: store reported symrefs The protocol has a capability which allows the server to tell us which refs are symrefs, so we can e.g. know which is the default branch. This capability is different from the ones we already support, as it's not setting a flag to true, but requires us to store a list of refspec-formatted mappings. This commit does not yet expose the information in the reference listing. --- src/transports/smart.c | 26 +++++++++++++-- src/transports/smart.h | 5 +-- src/transports/smart_protocol.c | 58 +++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index 69eaf9b78..2f3e77726 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -7,6 +7,7 @@ #include "git2.h" #include "smart.h" #include "refs.h" +#include "refspec.h" static int git_smart__recv_cb(gitno_buffer *buf) { @@ -63,7 +64,7 @@ static int git_smart__set_callbacks( return 0; } -int git_smart__update_heads(transport_smart *t) +int git_smart__update_heads(transport_smart *t, git_vector *symrefs) { size_t i; git_pkt *pkt; @@ -81,6 +82,19 @@ int git_smart__update_heads(transport_smart *t) return 0; } +static void free_symrefs(git_vector *symrefs) +{ + git_refspec *spec; + size_t i; + + git_vector_foreach(symrefs, i, spec) { + git_refspec__free(spec); + git__free(spec); + } + + git_vector_free(symrefs); +} + static int git_smart__connect( git_transport *transport, const char *url, @@ -94,6 +108,7 @@ static int git_smart__connect( int error; git_pkt *pkt; git_pkt_ref *first; + git_vector symrefs; git_smart_service_t service; if (git_smart__reset_stream(t, true) < 0) @@ -147,8 +162,11 @@ static int git_smart__connect( first = (git_pkt_ref *)git_vector_get(&t->refs, 0); + if ((error = git_vector_init(&symrefs, 1, NULL)) < 0) + return error; + /* Detect capabilities */ - if (git_smart__detect_caps(first, &t->caps) < 0) + if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0) return -1; /* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */ @@ -159,7 +177,9 @@ static int git_smart__connect( } /* Keep a list of heads for _ls */ - git_smart__update_heads(t); + git_smart__update_heads(t, &symrefs); + + free_symrefs(&symrefs); if (t->rpc && git_smart__reset_stream(t, false) < 0) return -1; diff --git a/src/transports/smart.h b/src/transports/smart.h index a2b6b2a71..f1fc29520 100644 --- a/src/transports/smart.h +++ b/src/transports/smart.h @@ -23,6 +23,7 @@ #define GIT_CAP_DELETE_REFS "delete-refs" #define GIT_CAP_REPORT_STATUS "report-status" #define GIT_CAP_THIN_PACK "thin-pack" +#define GIT_CAP_SYMREF "symref" enum git_pkt_type { GIT_PKT_CMD, @@ -154,7 +155,7 @@ typedef struct { /* smart_protocol.c */ int git_smart__store_refs(transport_smart *t, int flushes); -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps); +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs); int git_smart__push(git_transport *transport, git_push *push); int git_smart__negotiate_fetch( @@ -174,7 +175,7 @@ int git_smart__download_pack( int git_smart__negotiation_step(git_transport *transport, void *data, size_t len); int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out); -int git_smart__update_heads(transport_smart *t); +int git_smart__update_heads(transport_smart *t, git_vector *symrefs); /* smart_pkt.c */ int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len); diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index 5dd6bab24..bab0cf113 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -78,7 +78,52 @@ int git_smart__store_refs(transport_smart *t, int flushes) return flush; } -int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) +static int append_symref(const char **out, git_vector *symrefs, const char *ptr) +{ + int error; + const char *end; + git_buf buf = GIT_BUF_INIT; + git_refspec *mapping; + + ptr += strlen(GIT_CAP_SYMREF); + if (*ptr != '=') + goto on_invalid; + + ptr++; + if (!(end = strchr(ptr, ' ')) && + !(end = strchr(ptr, '\0'))) + goto on_invalid; + + if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0) + return error; + + /* symref mapping has refspec format */ + mapping = git__malloc(sizeof(git_refspec)); + GITERR_CHECK_ALLOC(mapping); + + error = git_refspec__parse(mapping, git_buf_cstr(&buf), true); + git_buf_free(&buf); + + /* if the error isn't OOM, then it's a parse error; let's use a nicer message */ + if (error < 0) { + if (giterr_last()->klass != GITERR_NOMEMORY) + goto on_invalid; + + return error; + } + + if ((error = git_vector_insert(symrefs, mapping)) < 0) + return error; + + *out = end; + return 0; + +on_invalid: + giterr_set(GITERR_NET, "remote sent invalid symref"); + return -1; +} + +int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs) { const char *ptr; @@ -141,6 +186,15 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) continue; } + if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) { + int error; + + if ((error = append_symref(&ptr, symrefs, ptr)) < 0) + return error; + + continue; + } + /* We don't know this capability, so skip it */ ptr = strchr(ptr, ' '); } @@ -969,7 +1023,7 @@ int git_smart__push(git_transport *transport, git_push *push) if (error < 0) goto done; - error = git_smart__update_heads(t); + error = git_smart__update_heads(t, NULL); } done: From 306475eb0173d7943a27afee6054af8d0f76bedd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 20 May 2014 09:55:26 +0200 Subject: [PATCH 48/95] remote: expose the remote's symref mappings Add a symref_target field to git_remote_head to expose the symref mappings to the user. --- include/git2/net.h | 5 +++++ src/transports/smart.c | 19 +++++++++++++++++++ src/transports/smart_pkt.c | 1 + tests/online/fetch.c | 18 ++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/include/git2/net.h b/include/git2/net.h index e70ba1f71..a727696a2 100644 --- a/include/git2/net.h +++ b/include/git2/net.h @@ -41,6 +41,11 @@ struct git_remote_head { git_oid oid; git_oid loid; char *name; + /** + * If the server send a symref mapping for this ref, this will + * point to the target. + */ + char *symref_target; }; /** diff --git a/src/transports/smart.c b/src/transports/smart.c index 2f3e77726..47ef5038c 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -75,6 +75,25 @@ int git_smart__update_heads(transport_smart *t, git_vector *symrefs) if (pkt->type != GIT_PKT_REF) continue; + if (symrefs) { + git_refspec *spec; + git_buf buf = GIT_BUF_INIT; + size_t j; + int error; + + git_vector_foreach(symrefs, j, spec) { + git_buf_clear(&buf); + if (git_refspec_src_matches(spec, ref->head.name) && + !(error = git_refspec_transform(&buf, spec, ref->head.name))) + ref->head.symref_target = git_buf_detach(&buf); + } + + git_buf_free(&buf); + + if (error < 0) + return error; + } + if (git_vector_insert(&t->heads, &ref->head) < 0) return -1; } diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c index e9376ae6f..b5f9d6dbe 100644 --- a/src/transports/smart_pkt.c +++ b/src/transports/smart_pkt.c @@ -433,6 +433,7 @@ void git_pkt_free(git_pkt *pkt) if (pkt->type == GIT_PKT_REF) { git_pkt_ref *p = (git_pkt_ref *) pkt; git__free(p->head.name); + git__free(p->head.symref_target); } if (pkt->type == GIT_PKT_OK) { diff --git a/tests/online/fetch.c b/tests/online/fetch.c index c54ec5673..f03a6faa6 100644 --- a/tests/online/fetch.c +++ b/tests/online/fetch.c @@ -184,3 +184,21 @@ void test_online_fetch__ls_disconnected(void) git_remote_free(remote); } + +void test_online_fetch__remote_symrefs(void) +{ + const git_remote_head **refs; + size_t refs_len; + git_remote *remote; + + 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)); + git_remote_disconnect(remote); + cl_git_pass(git_remote_ls(&refs, &refs_len, remote)); + + cl_assert_equal_s("HEAD", refs[0]->name); + cl_assert_equal_s("refs/heads/master", refs[0]->symref_target); + + git_remote_free(remote); +} From 04865aa05e9d16ad56920103678ee4c34578da78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 21 May 2014 10:01:44 +0200 Subject: [PATCH 49/95] local transport: expose the symref data When using the local transport, we always have the symbolic information available, so fill it. --- src/transports/local.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/transports/local.c b/src/transports/local.c index 2c17e6271..8d3619388 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -43,14 +43,19 @@ typedef struct { static int add_ref(transport_local *t, const char *name) { const char peeled[] = "^{}"; - git_oid head_oid; + git_reference *ref, *resolved; git_remote_head *head; + git_oid obj_id; git_object *obj = NULL, *target = NULL; git_buf buf = GIT_BUF_INIT; int error; - error = git_reference_name_to_id(&head_oid, t->repo, name); + if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) + return error; + + error = git_reference_resolve(&resolved, ref); if (error < 0) { + git_reference_free(ref); if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { /* This is actually okay. Empty repos often have a HEAD that * points to a nonexistent "refs/heads/master". */ @@ -60,13 +65,22 @@ static int add_ref(transport_local *t, const char *name) return error; } + git_oid_cpy(&obj_id, git_reference_target(resolved)); + git_reference_free(resolved); + head = git__calloc(1, sizeof(git_remote_head)); GITERR_CHECK_ALLOC(head); head->name = git__strdup(name); GITERR_CHECK_ALLOC(head->name); - git_oid_cpy(&head->oid, &head_oid); + git_oid_cpy(&head->oid, &obj_id); + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + head->symref_target = git__strdup(git_reference_symbolic_target(ref)); + GITERR_CHECK_ALLOC(head->symref_target); + } + git_reference_free(ref); if ((error = git_vector_insert(&t->refs, head)) < 0) { git__free(head->name); @@ -176,7 +190,7 @@ static int path_from_url_or_path(git_buf *local_path_out, const char *url_or_pat /* * Try to open the url as a git directory. The direction doesn't - * matter in this case because we're calulating the heads ourselves. + * matter in this case because we're calculating the heads ourselves. */ static int local_connect( git_transport *transport, From d22db24fb75134f30c3a72af0bc47fc7f0a07f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 21 May 2014 09:32:35 +0200 Subject: [PATCH 50/95] remote: add api to guess the remote's default branch If the remote supports the symref protocol extension, then we return that, otherwise we guess with git's rules. --- include/git2/remote.h | 18 ++++++++++ src/remote.c | 47 ++++++++++++++++++++++++++ tests/network/remote/defaultbranch.c | 50 ++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/network/remote/defaultbranch.c diff --git a/include/git2/remote.h b/include/git2/remote.h index 07cd2e7c6..28771ac42 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -623,6 +623,24 @@ GIT_EXTERN(int) git_remote_is_valid_name(const char *remote_name); */ GIT_EXTERN(int) git_remote_delete(git_remote *remote); +/** + * Retrieve the name of the remote's default branch + * + * The default branch of a repository is the branch which HEAD points + * to. If the remote does not support reporting this information + * directly, it performs the guess as git does; that is, if there are + * multiple branches which point to the same commit, the first one is + * chosen. If the master branch is a candidate, it wins. + * + * This function must only be called after connecting. + * + * @param out the buffern in which to store the reference name + * @param remote the remote + * @return 0, GIT_ENOTFOUND if the remote does not have any references + * or none of them point to HEAD's commit, or an error message. + */ +GIT_EXTERN(int) git_remote_default_branch(git_buf *out, git_remote *remote); + /** @} */ GIT_END_DECL #endif diff --git a/src/remote.c b/src/remote.c index bdc4791a9..f2e2e2f7a 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1885,3 +1885,50 @@ int git_remote_delete(git_remote *remote) return 0; } + +int git_remote_default_branch(git_buf *out, git_remote *remote) +{ + const git_remote_head **heads; + const git_remote_head *guess = NULL; + const git_oid *head_id; + size_t heads_len, i; + int error; + + if ((error = git_remote_ls(&heads, &heads_len, remote)) < 0) + return error; + + if (heads_len == 0) + return GIT_ENOTFOUND; + + git_buf_sanitize(out); + /* the first one must be HEAD so if that has the symref info, we're done */ + if (heads[0]->symref_target) + return git_buf_puts(out, heads[0]->symref_target); + + /* + * If there's no symref information, we have to look over them + * and guess. We return the first match unless the master + * branch is a candidate. Then we return the master branch. + */ + head_id = &heads[0]->oid; + + for (i = 1; i < heads_len; i++) { + if (git_oid_cmp(head_id, &heads[i]->oid)) + continue; + + if (!guess) { + guess = heads[i]; + continue; + } + + if (!git__strcmp(GIT_REFS_HEADS_MASTER_FILE, heads[i]->name)) { + guess = heads[i]; + break; + } + } + + if (!guess) + return GIT_ENOTFOUND; + + return git_buf_puts(out, guess->name); +} diff --git a/tests/network/remote/defaultbranch.c b/tests/network/remote/defaultbranch.c new file mode 100644 index 000000000..fa3a329db --- /dev/null +++ b/tests/network/remote/defaultbranch.c @@ -0,0 +1,50 @@ +#include "clar_libgit2.h" +#include "buffer.h" +#include "refspec.h" +#include "remote.h" + +static git_remote *g_remote; +static git_repository *g_repo_a, *g_repo_b; + +void test_network_remote_defaultbranch__initialize(void) +{ + g_repo_a = cl_git_sandbox_init("testrepo.git"); + cl_git_pass(git_repository_init(&g_repo_b, "repo-b.git", true)); + cl_git_pass(git_remote_create(&g_remote, g_repo_b, "origin", git_repository_path(g_repo_a))); +} + +void test_network_remote_defaultbranch__cleanup(void) +{ + git_remote_free(g_remote); + git_repository_free(g_repo_b); + + cl_git_sandbox_cleanup(); + cl_fixture_cleanup("repo-b.git"); +} + +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)); + cl_git_pass(git_remote_default_branch(&name, g_remote)); + cl_assert_equal_s(should, name.ptr); + git_buf_free(&name); +} + +void test_network_remote_defaultbranch__master(void) +{ + assert_default_branch("refs/heads/master"); +} + +void test_network_remote_defaultbranch__master_does_not_win(void) +{ + cl_git_pass(git_repository_set_head(g_repo_a, "refs/heads/not-good", NULL, NULL)); + assert_default_branch("refs/heads/not-good"); +} + +void test_network_remote_defaultbranch__master_on_detached(void) +{ + cl_git_pass(git_repository_detach_head(g_repo_a, NULL, NULL)); + assert_default_branch("refs/heads/master"); +} From cdb8a608249b3d0b9e8fe1b3ea1e9c1aa731ab8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 21 May 2014 11:51:33 +0200 Subject: [PATCH 51/95] clone: make use of the remote's default branch guessing Let's use the remote's default branch guessing instead of reinventing one ourselves with callbacks. --- src/clone.c | 84 +++++++++++------------------------------------------ 1 file changed, 17 insertions(+), 67 deletions(-) diff --git a/src/clone.c b/src/clone.c index 9ac9eb2a1..b6a0f03d0 100644 --- a/src/clone.c +++ b/src/clone.c @@ -108,51 +108,10 @@ static int create_tracking_branch( struct head_info { git_repository *repo; git_oid remote_head_oid; - git_buf branchname; const git_refspec *refspec; bool found; }; -static int reference_matches_remote_head( - const char *reference_name, - void *payload) -{ - struct head_info *head_info = (struct head_info *)payload; - git_oid oid; - int error; - - /* TODO: Should we guard against references - * which name doesn't start with refs/heads/ ? - */ - - error = git_reference_name_to_id(&oid, head_info->repo, reference_name); - if (error == GIT_ENOTFOUND) { - /* If the reference doesn't exists, it obviously cannot match the - * expected oid. */ - giterr_clear(); - return 0; - } - - if (!error && !git_oid__cmp(&head_info->remote_head_oid, &oid)) { - /* Determine the local reference name from the remote tracking one */ - error = git_refspec_rtransform( - &head_info->branchname, head_info->refspec, reference_name); - - if (!error && - git_buf_len(&head_info->branchname) > 0 && - !(error = git_buf_sets( - &head_info->branchname, - git_buf_cstr(&head_info->branchname) + - strlen(GIT_REFS_HEADS_DIR)))) - { - head_info->found = true; - error = GIT_ITEROVER; - } - } - - return error; -} - static int update_head_to_new_branch( git_repository *repo, const git_oid *target, @@ -161,6 +120,10 @@ static int update_head_to_new_branch( const char *reflog_message) { git_reference *tracking_branch = NULL; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + int error = create_tracking_branch(&tracking_branch, repo, target, name, signature, reflog_message); @@ -190,6 +153,7 @@ static int update_head_to_remote( const git_remote_head *remote_head, **refs; struct head_info head_info; git_buf remote_master_name = GIT_BUF_INIT; + git_buf branch = GIT_BUF_INIT; if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) return error; @@ -199,15 +163,22 @@ static int update_head_to_remote( return setup_tracking_config( repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE); + memset(&head_info, 0, sizeof(head_info)); + error = git_remote_default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE); + } else { + head_info.found = 1; + } + /* Get the remote's HEAD. This is always the first ref in the list. */ remote_head = refs[0]; assert(remote_head); - memset(&head_info, 0, sizeof(head_info)); git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); head_info.repo = repo; head_info.refspec = - git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE); + git_remote__matching_refspec(remote, git_buf_cstr(&branch)); if (head_info.refspec == NULL) { memset(&dummy_spec, 0, sizeof(git_refspec)); @@ -218,35 +189,14 @@ static int update_head_to_remote( if ((error = git_refspec_transform( &remote_master_name, head_info.refspec, - GIT_REFS_HEADS_MASTER_FILE)) < 0) + git_buf_cstr(&branch))) < 0) return error; - /* Check to see if the remote HEAD points to the remote master */ - error = reference_matches_remote_head( - git_buf_cstr(&remote_master_name), &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - if (head_info.found) { error = update_head_to_new_branch( repo, &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), - signature, reflog_message); - goto cleanup; - } - - /* Not master. Check all the other refs. */ - error = git_reference_foreach_name( - repo, reference_matches_remote_head, &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { - error = update_head_to_new_branch( - repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), + git_buf_cstr(&branch), signature, reflog_message); } else { error = git_repository_set_head_detached( @@ -255,7 +205,7 @@ static int update_head_to_remote( cleanup: git_buf_free(&remote_master_name); - git_buf_free(&head_info.branchname); + git_buf_free(&branch); return error; } From 2a597116583696486141261a8b459319b513facf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 21 May 2014 11:54:10 +0200 Subject: [PATCH 52/95] clone: get rid of head_info Since we no longer need to push data to callbacks, there's no need for this truct. --- src/clone.c | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/clone.c b/src/clone.c index b6a0f03d0..f19771c1e 100644 --- a/src/clone.c +++ b/src/clone.c @@ -105,13 +105,6 @@ static int create_tracking_branch( git_reference_name(*branch)); } -struct head_info { - git_repository *repo; - git_oid remote_head_oid; - const git_refspec *refspec; - bool found; -}; - static int update_head_to_new_branch( git_repository *repo, const git_oid *target, @@ -147,11 +140,11 @@ static int update_head_to_remote( const git_signature *signature, const char *reflog_message) { - int error = 0; + int error = 0, found_branch = 0; size_t refs_len; - git_refspec dummy_spec; + git_refspec dummy_spec, *refspec; const git_remote_head *remote_head, **refs; - struct head_info head_info; + const git_oid *remote_head_id; git_buf remote_master_name = GIT_BUF_INIT; git_buf branch = GIT_BUF_INIT; @@ -163,47 +156,43 @@ static int update_head_to_remote( return setup_tracking_config( repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE); - memset(&head_info, 0, sizeof(head_info)); error = git_remote_default_branch(&branch, remote); if (error == GIT_ENOTFOUND) { git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE); } else { - head_info.found = 1; + found_branch = 1; } /* Get the remote's HEAD. This is always the first ref in the list. */ remote_head = refs[0]; assert(remote_head); - git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); - head_info.repo = repo; - head_info.refspec = - git_remote__matching_refspec(remote, git_buf_cstr(&branch)); + remote_head_id = &remote_head->oid; + refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch)); - if (head_info.refspec == NULL) { + if (refspec == NULL) { memset(&dummy_spec, 0, sizeof(git_refspec)); - head_info.refspec = &dummy_spec; + refspec = &dummy_spec; } /* Determine the remote tracking reference name from the local master */ if ((error = git_refspec_transform( &remote_master_name, - head_info.refspec, + refspec, git_buf_cstr(&branch))) < 0) return error; - if (head_info.found) { + if (found_branch) { error = update_head_to_new_branch( repo, - &head_info.remote_head_oid, + remote_head_id, git_buf_cstr(&branch), signature, reflog_message); } else { error = git_repository_set_head_detached( - repo, &head_info.remote_head_oid, signature, reflog_message); + repo, remote_head_id, signature, reflog_message); } -cleanup: git_buf_free(&remote_master_name); git_buf_free(&branch); return error; From 7230330740eff79dc910d2a20418545bdcf2f65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 21 May 2014 12:45:22 +0200 Subject: [PATCH 53/95] travis: build on osx too --- .travis.yml | 16 ++++++++++++---- script/install-deps-linux.sh | 6 ++++++ script/install-deps-osx.sh | 5 +++++ 3 files changed, 23 insertions(+), 4 deletions(-) create mode 100755 script/install-deps-linux.sh create mode 100755 script/install-deps-osx.sh diff --git a/.travis.yml b/.travis.yml index fcae726dd..bab02bb44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ language: c +os: + - linux + - osx + compiler: - gcc - clang @@ -17,17 +21,21 @@ env: matrix: fast_finish: true + exclude: + - os: osx + compiler: gcc include: - compiler: i586-mingw32msvc-gcc env: OPTIONS="-DBUILD_CLAR=OFF -DWIN32=ON -DMINGW=ON -DUSE_SSH=OFF" + os: linux - compiler: gcc env: COVERITY=1 + os: linux allow_failures: - env: COVERITY=1 install: - - sudo apt-get -qq update - - sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server + - ./script/install-deps-${TRAVIS_OS_NAME}.sh # Run the Build script and tests script: @@ -35,8 +43,8 @@ script: # Run Tests after_success: - - sudo apt-get -qq install valgrind - - valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install valgrind; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then valgrind --leak-check=full --show-reachable=yes --suppressions=./libgit2_clar.supp _build/libgit2_clar -ionline; fi # Only watch the development branch branches: diff --git a/script/install-deps-linux.sh b/script/install-deps-linux.sh new file mode 100755 index 000000000..347922b89 --- /dev/null +++ b/script/install-deps-linux.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +set -x + +sudo apt-get -qq update && +sudo apt-get -qq install cmake libssh2-1-dev openssh-client openssh-server diff --git a/script/install-deps-osx.sh b/script/install-deps-osx.sh new file mode 100755 index 000000000..c2e0162d8 --- /dev/null +++ b/script/install-deps-osx.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +set -x + +brew install libssh2 cmake From ead9c591ef6cbad37198421637ae31eec5ab4472 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 21 May 2014 17:25:00 -0700 Subject: [PATCH 54/95] Include windows.h on win32 for Sleep --- examples/status.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/status.c b/examples/status.c index 9c99744cb..a59f34454 100644 --- a/examples/status.c +++ b/examples/status.c @@ -14,9 +14,10 @@ #include "common.h" #ifdef _WIN32 -#define sleep(a) Sleep(a * 1000) +# include +# define sleep(a) Sleep(a * 1000) #else -#include +# include #endif /** From 4c4408c351650dac84c81a67a321a4d92cc85ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 22 May 2014 12:28:39 +0200 Subject: [PATCH 55/95] Plug leaks and fix a C99-ism We have too many places where we repeat free code, so when adding the new free to the generic code, it didn't take for the local transport. While there, fix a C99-ism that sneaked through. --- src/clone.c | 3 ++- src/transports/local.c | 19 +++++++++++-------- src/transports/smart_protocol.c | 7 +++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/clone.c b/src/clone.c index f19771c1e..8381ec63c 100644 --- a/src/clone.c +++ b/src/clone.c @@ -113,11 +113,12 @@ static int update_head_to_new_branch( const char *reflog_message) { git_reference *tracking_branch = NULL; + int error; if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) name += strlen(GIT_REFS_HEADS_DIR); - int error = create_tracking_branch(&tracking_branch, repo, target, name, + error = create_tracking_branch(&tracking_branch, repo, target, name, signature, reflog_message); if (!error) diff --git a/src/transports/local.c b/src/transports/local.c index 8d3619388..038337d72 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -40,6 +40,13 @@ typedef struct { have_refs : 1; } transport_local; +static void free_head(git_remote_head *head) +{ + git__free(head->name); + git__free(head->symref_target); + git__free(head); +} + static int add_ref(transport_local *t, const char *name) { const char peeled[] = "^{}"; @@ -83,8 +90,7 @@ static int add_ref(transport_local *t, const char *name) git_reference_free(ref); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); return error; } @@ -117,8 +123,7 @@ static int add_ref(transport_local *t, const char *name) git_oid_cpy(&head->oid, git_object_id(target)); if ((error = git_vector_insert(&t->refs, head)) < 0) { - git__free(head->name); - git__free(head); + free_head(head); } } @@ -640,10 +645,8 @@ static void local_free(git_transport *transport) size_t i; git_remote_head *head; - git_vector_foreach(&t->refs, i, head) { - git__free(head->name); - git__free(head); - } + git_vector_foreach(&t->refs, i, head) + free_head(head); git_vector_free(&t->refs); diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c index bab0cf113..a52aacc60 100644 --- a/src/transports/smart_protocol.c +++ b/src/transports/smart_protocol.c @@ -26,17 +26,16 @@ int git_smart__store_refs(transport_smart *t, int flushes) int error, flush = 0, recvd; const char *line_end = NULL; git_pkt *pkt = NULL; - git_pkt_ref *ref; size_t i; /* Clear existing refs in case git_remote_connect() is called again * after git_remote_disconnect(). */ - git_vector_foreach(refs, i, ref) { - git__free(ref->head.name); - git__free(ref); + git_vector_foreach(refs, i, pkt) { + git_pkt_free(pkt); } git_vector_clear(refs); + pkt = NULL; do { if (buf->offset > 0) From 9331f98acaffd377a8076ab111bed84ff89e8e72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 22 May 2014 12:52:31 +0200 Subject: [PATCH 56/95] smart: initialize the error variable --- src/transports/smart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/transports/smart.c b/src/transports/smart.c index c9845bff9..a5c3e82dc 100644 --- a/src/transports/smart.c +++ b/src/transports/smart.c @@ -79,7 +79,7 @@ int git_smart__update_heads(transport_smart *t, git_vector *symrefs) git_refspec *spec; git_buf buf = GIT_BUF_INIT; size_t j; - int error; + int error = 0; git_vector_foreach(symrefs, j, spec) { git_buf_clear(&buf); From 052a2ffde4d8cf0d2b07c70956631d97875ad5ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 22 May 2014 16:01:02 +0200 Subject: [PATCH 57/95] index: check for valid filemodes on add --- src/index.c | 14 ++++++++++++++ tests/index/filemodes.c | 15 +++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/index.c b/src/index.c index 8a7f29279..b63a0bec6 100644 --- a/src/index.c +++ b/src/index.c @@ -1104,6 +1104,15 @@ int git_index_remove_bypath(git_index *index, const char *path) return 0; } +static bool valid_filemode(const int filemode) +{ + return (filemode == GIT_FILEMODE_BLOB || + filemode == GIT_FILEMODE_BLOB_EXECUTABLE || + filemode == GIT_FILEMODE_LINK || + filemode == GIT_FILEMODE_COMMIT); +} + + int git_index_add(git_index *index, const git_index_entry *source_entry) { git_index_entry *entry = NULL; @@ -1111,6 +1120,11 @@ int git_index_add(git_index *index, const git_index_entry *source_entry) assert(index && source_entry && source_entry->path); + if (!valid_filemode(source_entry->mode)) { + giterr_set(GITERR_INDEX, "invalid filemode"); + return -1; + } + if ((ret = index_entry_dup(&entry, source_entry)) < 0 || (ret = index_insert(index, &entry, 1)) < 0) return ret; diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index 013932696..e00b9c975 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -152,3 +152,18 @@ void test_index_filemodes__trusted(void) git_index_free(index); } + +void test_index_filemodes__invalid(void) +{ + git_index *index; + git_index_entry entry; + + cl_git_pass(git_repository_index(&index, g_repo)); + + entry.path = "foo"; + entry.mode = GIT_OBJ_BLOB; + cl_git_fail(git_index_add(index, &entry)); + + entry.mode = GIT_FILEMODE_BLOB; + cl_git_pass(git_index_add(index, &entry)); +} From 97fc71ab3b935df5f32faae13035e40eeb03c07f Mon Sep 17 00:00:00 2001 From: Eoin Coffey Date: Thu, 22 May 2014 16:01:45 -0600 Subject: [PATCH 58/95] Add support for --author flag in example log implementation --- examples/log.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/log.c b/examples/log.c index 471c5ff96..655b95d10 100644 --- a/examples/log.c +++ b/examples/log.c @@ -54,7 +54,7 @@ struct log_options { int min_parents, max_parents; git_time_t before; git_time_t after; - char *author; + const char *author; char *committer; }; @@ -75,6 +75,7 @@ int main(int argc, char *argv[]) git_oid oid; git_commit *commit = NULL; git_pathspec *ps = NULL; + const git_signature *sig; git_threads_init(); @@ -128,6 +129,12 @@ int main(int argc, char *argv[]) continue; } + if (opt.author != NULL) { + if ((sig = git_commit_author(commit)) == NULL || + strstr(sig->name, opt.author) == NULL) + continue; + } + if (count++ < opt.skip) continue; if (opt.limit != -1 && printed++ >= opt.limit) { @@ -401,6 +408,8 @@ static int parse_options( set_sorting(s, GIT_SORT_TOPOLOGICAL); else if (!strcmp(a, "--reverse")) set_sorting(s, GIT_SORT_REVERSE); + else if (match_str_arg(&opt->author, &args, "--author")) + /** Found valid --author */; else if (match_str_arg(&s->repodir, &args, "--git-dir")) /** Found git-dir. */; else if (match_int_arg(&opt->skip, &args, "--skip", 0)) From 530594c0aa04df31e3cef331f6dad8083f66f15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 23 May 2014 05:53:41 +0200 Subject: [PATCH 59/95] odb: clear backend errors on successful read We go through the different backends in order, so it's not an error if at least one of the backends has the data we want. --- src/odb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/odb.c b/src/odb.c index 20a3f6c6e..a4fc02686 100644 --- a/src/odb.c +++ b/src/odb.c @@ -783,6 +783,7 @@ int git_odb_read(git_odb_object **out, git_odb *db, const git_oid *id) return error; } + giterr_clear(); if ((object = odb_object__alloc(id, &raw)) == NULL) return -1; From 31b0cb518f11d4ec8a95df54d077d51b05fa899c Mon Sep 17 00:00:00 2001 From: Michael Anderson Date: Thu, 22 May 2014 17:16:21 +0800 Subject: [PATCH 60/95] Fixed miscellaneous documentation errors. --- include/git2/cherrypick.h | 2 +- include/git2/errors.h | 32 ++++++++++++++++---------------- include/git2/merge.h | 10 +++++----- include/git2/reflog.h | 2 +- include/git2/refs.h | 4 +--- include/git2/repository.h | 2 +- include/git2/revert.h | 7 +++---- include/git2/signature.h | 2 +- include/git2/submodule.h | 2 +- include/git2/tree.h | 2 +- 10 files changed, 31 insertions(+), 34 deletions(-) diff --git a/include/git2/cherrypick.h b/include/git2/cherrypick.h index e998d325f..9eccb0af9 100644 --- a/include/git2/cherrypick.h +++ b/include/git2/cherrypick.h @@ -56,7 +56,7 @@ GIT_EXTERN(int) git_cherry_pick_init_options( * @param cherry_pick_commit the commit to cherry-pick * @param our_commit the commit to revert against (eg, HEAD) * @param mainline the parent of the revert commit, if it is a merge - * @param merge_tree_opts the merge tree options (or null for defaults) + * @param merge_options the merge options (or null for defaults) * @return zero on success, -1 on failure. */ GIT_EXTERN(int) git_cherry_pick_commit( diff --git a/include/git2/errors.h b/include/git2/errors.h index e22f0d86d..c914653fc 100644 --- a/include/git2/errors.h +++ b/include/git2/errors.h @@ -19,13 +19,13 @@ GIT_BEGIN_DECL /** Generic return codes */ typedef enum { - GIT_OK = 0, /*< No error */ + GIT_OK = 0, /**< No error */ - GIT_ERROR = -1, /*< Generic error */ - GIT_ENOTFOUND = -3, /*< Requested object could not be found */ - GIT_EEXISTS = -4, /*< Object exists preventing operation */ - GIT_EAMBIGUOUS = -5, /*< More than one object matches */ - GIT_EBUFS = -6, /*< Output buffer too short to hold data */ + GIT_ERROR = -1, /**< Generic error */ + GIT_ENOTFOUND = -3, /**< Requested object could not be found */ + GIT_EEXISTS = -4, /**< Object exists preventing operation */ + GIT_EAMBIGUOUS = -5, /**< More than one object matches */ + GIT_EBUFS = -6, /**< Output buffer too short to hold data */ /* GIT_EUSER is a special error that is never generated by libgit2 * code. You can return it from a callback (e.g to stop an iteration) @@ -33,17 +33,17 @@ typedef enum { */ GIT_EUSER = -7, - GIT_EBAREREPO = -8, /*< Operation not allowed on bare repository */ - GIT_EUNBORNBRANCH = -9, /*< HEAD refers to branch with no commits */ - GIT_EUNMERGED = -10, /*< Merge in progress prevented operation */ - GIT_ENONFASTFORWARD = -11, /*< Reference was not fast-forwardable */ - GIT_EINVALIDSPEC = -12, /*< Name/ref spec was not in a valid format */ - GIT_EMERGECONFLICT = -13, /*< Merge conflicts prevented operation */ - GIT_ELOCKED = -14, /*< Lock file prevented operation */ - GIT_EMODIFIED = -15, /*< Reference value does not match expected */ + GIT_EBAREREPO = -8, /**< Operation not allowed on bare repository */ + GIT_EUNBORNBRANCH = -9, /**< HEAD refers to branch with no commits */ + GIT_EUNMERGED = -10, /**< Merge in progress prevented operation */ + GIT_ENONFASTFORWARD = -11, /**< Reference was not fast-forwardable */ + GIT_EINVALIDSPEC = -12, /**< Name/ref spec was not in a valid format */ + GIT_EMERGECONFLICT = -13, /**< Merge conflicts prevented operation */ + GIT_ELOCKED = -14, /**< Lock file prevented operation */ + GIT_EMODIFIED = -15, /**< Reference value does not match expected */ - GIT_PASSTHROUGH = -30, /*< Internal only */ - GIT_ITEROVER = -31, /*< Signals end of iteration with iterator */ + GIT_PASSTHROUGH = -30, /**< Internal only */ + GIT_ITEROVER = -31, /**< Signals end of iteration with iterator */ } git_error_code; /** diff --git a/include/git2/merge.h b/include/git2/merge.h index abbc3a5bb..7915050b0 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -378,8 +378,8 @@ GIT_EXTERN(int) git_merge_head_from_id( /** * Gets the commit ID that the given `git_merge_head` refers to. * - * @param id pointer to commit id to be filled in * @param head the given merge head + * @return commit id */ GIT_EXTERN(const git_oid *) git_merge_head_id( const git_merge_head *head); @@ -424,8 +424,8 @@ GIT_EXTERN(int) git_merge_file( * @param out The git_merge_file_result to be filled in * @param repo The repository * @param ancestor The index entry for the ancestor file (stage level 1) - * @param our_path The index entry for our file (stage level 2) - * @param their_path The index entry for their file (stage level 3) + * @param ours The index entry for our file (stage level 2) + * @param theirs The index entry for their file (stage level 3) * @param opts The merge file options or NULL * @return 0 on success or error code */ @@ -497,8 +497,8 @@ GIT_EXTERN(int) git_merge_commits( * completes, resolve any conflicts and prepare a commit. * * @param repo the repository to merge - * @param merge_heads the heads to merge into - * @param merge_heads_len the number of heads to merge + * @param their_heads the heads to merge into + * @param their_heads_len the number of heads to merge * @param merge_opts merge options * @param checkout_opts checkout options * @return 0 on success or error code diff --git a/include/git2/reflog.h b/include/git2/reflog.h index df06e1b8e..ac42a231c 100644 --- a/include/git2/reflog.h +++ b/include/git2/reflog.h @@ -69,7 +69,7 @@ GIT_EXTERN(int) git_reflog_append(git_reflog *reflog, const git_oid *id, const g * * @param repo the repository * @param old_name the old name of the reference - * @param new_name the new name of the reference + * @param name the new name of the reference * @return 0 on success, GIT_EINVALIDSPEC or an error code */ GIT_EXTERN(int) git_reflog_rename(git_repository *repo, const char *old_name, const char *name); diff --git a/include/git2/refs.h b/include/git2/refs.h index ae2d379d9..e5bb15c7c 100644 --- a/include/git2/refs.h +++ b/include/git2/refs.h @@ -178,7 +178,6 @@ GIT_EXTERN(int) git_reference_symbolic_create(git_reference **out, git_repositor * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @param force Overwrite existing references * @param signature The identity that will used to populate the reflog entry * @param log_message The one line long message to be appended to the reflog * @return 0 on success, GIT_EEXISTS, GIT_EINVALIDSPEC or an error code @@ -221,7 +220,6 @@ GIT_EXTERN(int) git_reference_create(git_reference **out, git_repository *repo, * @param name The name of the reference * @param id The object id pointed to by the reference. * @param force Overwrite existing references - * @param force Overwrite existing references * @param current_id The expected value of the reference at the time of update * @param signature The identity that will used to populate the reflog entry * @param log_message The one line long message to be appended to the reflog @@ -415,7 +413,7 @@ GIT_EXTERN(int) git_reference_delete(git_reference *ref); * This method removes the named reference from the repository without * looking at its old value. * - * @param ref The reference to remove + * @param name The reference to remove * @return 0 or an error code */ GIT_EXTERN(int) git_reference_remove(git_repository *repo, const char *name); diff --git a/include/git2/repository.h b/include/git2/repository.h index 037cb3f96..6a8ff4545 100644 --- a/include/git2/repository.h +++ b/include/git2/repository.h @@ -649,7 +649,7 @@ GIT_EXTERN(int) git_repository_set_head_detached( * * @param repo Repository pointer * @param signature The identity that will used to populate the reflog entry - * @param log_message The one line long message to be appended to the reflog + * @param reflog_message The one line long message to be appended to the reflog * @return 0 on success, GIT_EUNBORNBRANCH when HEAD points to a non existing * branch or an error code */ diff --git a/include/git2/revert.h b/include/git2/revert.h index da37fbe7b..fc1767c93 100644 --- a/include/git2/revert.h +++ b/include/git2/revert.h @@ -56,7 +56,7 @@ GIT_EXTERN(int) git_revert_init_options( * @param revert_commit the commit to revert * @param our_commit the commit to revert against (eg, HEAD) * @param mainline the parent of the revert commit, if it is a merge - * @param merge_tree_opts the merge tree options (or null for defaults) + * @param merge_options the merge options (or null for defaults) * @return zero on success, -1 on failure. */ int git_revert_commit( @@ -71,9 +71,8 @@ int git_revert_commit( * Reverts the given commit, producing changes in the working directory. * * @param repo the repository to revert - * @param commits the commits to revert - * @param commits_len the number of commits to revert - * @param flags merge flags + * @param commit the commit to revert + * @param given_opts merge flags * @return zero on success, -1 on failure. */ GIT_EXTERN(int) git_revert( diff --git a/include/git2/signature.h b/include/git2/signature.h index a1dd1ec7a..feb1b4073 100644 --- a/include/git2/signature.h +++ b/include/git2/signature.h @@ -69,7 +69,7 @@ GIT_EXTERN(int) git_signature_default(git_signature **out, git_repository *repo) * Call `git_signature_free()` to free the data. * * @param dest pointer where to store the copy - * @param entry signature to duplicate + * @param sig signature to duplicate * @return 0 or an error code */ GIT_EXTERN(int) git_signature_dup(git_signature **dest, const git_signature *sig); diff --git a/include/git2/submodule.h b/include/git2/submodule.h index 28e235725..864d1c58c 100644 --- a/include/git2/submodule.h +++ b/include/git2/submodule.h @@ -283,7 +283,7 @@ GIT_EXTERN(const char *) git_submodule_url(git_submodule *submodule); * Resolve a submodule url relative to the given repository. * * @param out buffer to store the absolute submodule url in - * @param repository Pointer to repository object + * @param repo Pointer to repository object * @param url Relative url * @return 0 or an error code */ diff --git a/include/git2/tree.h b/include/git2/tree.h index 6669652ae..56922d40b 100644 --- a/include/git2/tree.h +++ b/include/git2/tree.h @@ -151,7 +151,7 @@ GIT_EXTERN(int) git_tree_entry_bypath( * and must be freed explicitly with `git_tree_entry_free()`. * * @param dest pointer where to store the copy - * @param entry tree entry to duplicate + * @param source tree entry to duplicate * @return 0 or an error code */ GIT_EXTERN(int) git_tree_entry_dup(git_tree_entry **dest, const git_tree_entry *source); From 161e6dc1cacb724543c81ee0a62ed28742a81190 Mon Sep 17 00:00:00 2001 From: Eoin Coffey Date: Fri, 23 May 2014 12:27:16 -0600 Subject: [PATCH 61/95] Add --committer option, and break out helper function --- examples/log.c | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/examples/log.c b/examples/log.c index 655b95d10..5a43f632a 100644 --- a/examples/log.c +++ b/examples/log.c @@ -44,6 +44,7 @@ struct log_state { /** utility functions that are called to configure the walker */ static void set_sorting(struct log_state *s, unsigned int sort_mode); +static int signature_does_not_match(const git_signature *sig, const char *filter); static void push_rev(struct log_state *s, git_object *obj, int hide); static int add_revision(struct log_state *s, const char *revstr); @@ -55,7 +56,7 @@ struct log_options { git_time_t before; git_time_t after; const char *author; - char *committer; + const char *committer; }; /** utility functions that parse options and help with log output */ @@ -75,7 +76,6 @@ int main(int argc, char *argv[]) git_oid oid; git_commit *commit = NULL; git_pathspec *ps = NULL; - const git_signature *sig; git_threads_init(); @@ -129,11 +129,11 @@ int main(int argc, char *argv[]) continue; } - if (opt.author != NULL) { - if ((sig = git_commit_author(commit)) == NULL || - strstr(sig->name, opt.author) == NULL) - continue; - } + if (signature_does_not_match(git_commit_author(commit), opt.author)) + continue; + + if (signature_does_not_match(git_commit_committer(commit), opt.committer)) + continue; if (count++ < opt.skip) continue; @@ -179,6 +179,18 @@ int main(int argc, char *argv[]) return 0; } +/** Determine if the given git_signature does not contain the filter text. */ +static int signature_does_not_match(const git_signature *sig, const char *filter) { + if (filter == NULL) + return 0; + + if (sig == NULL || + (strstr(sig->name, filter) == NULL && + strstr(sig->email, filter) == NULL)) + return 1; + return 0; +} + /** Push object (for hide or show) onto revwalker. */ static void push_rev(struct log_state *s, git_object *obj, int hide) { @@ -410,6 +422,8 @@ static int parse_options( set_sorting(s, GIT_SORT_REVERSE); else if (match_str_arg(&opt->author, &args, "--author")) /** Found valid --author */; + else if (match_str_arg(&opt->committer, &args, "--committer")) + /** Found valid --committer */; else if (match_str_arg(&s->repodir, &args, "--git-dir")) /** Found git-dir. */; else if (match_int_arg(&opt->skip, &args, "--skip", 0)) From 26cce32133363133b479cfd811523270db851466 Mon Sep 17 00:00:00 2001 From: Eoin Coffey Date: Fri, 23 May 2014 12:59:19 -0600 Subject: [PATCH 62/95] Add support for --grep --- examples/log.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/examples/log.c b/examples/log.c index 5a43f632a..965f854d7 100644 --- a/examples/log.c +++ b/examples/log.c @@ -44,7 +44,6 @@ struct log_state { /** utility functions that are called to configure the walker */ static void set_sorting(struct log_state *s, unsigned int sort_mode); -static int signature_does_not_match(const git_signature *sig, const char *filter); static void push_rev(struct log_state *s, git_object *obj, int hide); static int add_revision(struct log_state *s, const char *revstr); @@ -57,6 +56,7 @@ struct log_options { git_time_t after; const char *author; const char *committer; + const char *grep; }; /** utility functions that parse options and help with log output */ @@ -66,6 +66,9 @@ static void print_time(const git_time *intime, const char *prefix); static void print_commit(git_commit *commit); static int match_with_parent(git_commit *commit, int i, git_diff_options *); +/** utility functions for filtering */ +static int signature_does_not_match(const git_signature *sig, const char *filter); +static int log_message_does_not_match(const git_commit *commit, const char *filter); int main(int argc, char *argv[]) { @@ -135,6 +138,9 @@ int main(int argc, char *argv[]) if (signature_does_not_match(git_commit_committer(commit), opt.committer)) continue; + if (log_message_does_not_match(commit, opt.grep)) + continue; + if (count++ < opt.skip) continue; if (opt.limit != -1 && printed++ >= opt.limit) { @@ -188,6 +194,20 @@ static int signature_does_not_match(const git_signature *sig, const char *filter (strstr(sig->name, filter) == NULL && strstr(sig->email, filter) == NULL)) return 1; + + return 0; +} + +static int log_message_does_not_match(const git_commit *commit, const char *filter) { + const char *message = NULL; + + if (filter == NULL) + return 0; + + if ((message = git_commit_message(commit)) == NULL || + strstr(message, filter) == NULL) + return 1; + return 0; } @@ -424,6 +444,8 @@ static int parse_options( /** Found valid --author */; else if (match_str_arg(&opt->committer, &args, "--committer")) /** Found valid --committer */; + else if (match_str_arg(&opt->grep, &args, "--grep")) + /** Found valid --grep */; else if (match_str_arg(&s->repodir, &args, "--git-dir")) /** Found git-dir. */; else if (match_int_arg(&opt->skip, &args, "--skip", 0)) From 87493bca9c5c14f99adea5ceadf6caa8005a6ada Mon Sep 17 00:00:00 2001 From: Eoin Coffey Date: Fri, 23 May 2014 13:00:30 -0600 Subject: [PATCH 63/95] Remove simple --author, --committer, and --grep from PROJECTS --- PROJECTS.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/PROJECTS.md b/PROJECTS.md index 9472fb43c..d17214471 100644 --- a/PROJECTS.md +++ b/PROJECTS.md @@ -39,12 +39,6 @@ These are good small projects to get started with libgit2. the data is available, you would just need to add the code into the `print_commit()` routine (along with a way of passing the option into that function). - * For `examples/log.c`, implement any one of `--author=<...>`, - `--committer=<...>`, or `--grep=<...>` but just use simple string - match with `strstr()` instead of full regular expression - matching. (I.e. I'm suggesting implementing this as if - `--fixed-strings` was always turned on, because it will be a simpler - project.) * As an extension to the matching idea for `examples/log.c`, add the `-i` option to use `strcasestr()` for matches. * For `examples/log.c`, implement the `--first-parent` option now that From 517341c5d8b316f5590d55a4913a65c78ab05973 Mon Sep 17 00:00:00 2001 From: Edward Lee Date: Fri, 23 May 2014 22:41:35 -0400 Subject: [PATCH 64/95] Address style concerns in setting mkdir/copy flags. --- src/repository.c | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/src/repository.c b/src/repository.c index 695351977..e8d50aed3 100644 --- a/src/repository.c +++ b/src/repository.c @@ -1232,15 +1232,10 @@ static int repo_init_structure( } if (tdir) { - if (chmod) { - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); - } else { - error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS | - GIT_CPDIR_SIMPLE_TO_MODE, dmode); - } + uint32_t cpflags = GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_SIMPLE_TO_MODE; + if (opts->mode != GIT_REPOSITORY_INIT_SHARED_UMASK) + cpflags |= GIT_CPDIR_CHMOD_DIRS; + error = git_futils_cp_r(tdir, repo_dir, cpflags, dmode); } git_buf_free(&template_buf); @@ -1263,13 +1258,12 @@ static int repo_init_structure( */ for (tpl = repo_template; !error && tpl->path; ++tpl) { if (!tpl->content) { - if (chmod) { - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, GIT_MKDIR_PATH | GIT_MKDIR_CHMOD); - } else { - error = git_futils_mkdir( - tpl->path, repo_dir, dmode, GIT_MKDIR_PATH); - } + uint32_t mkdir_flags = GIT_MKDIR_PATH; + if (chmod) + mkdir_flags |= GIT_MKDIR_CHMOD; + + error = git_futils_mkdir( + tpl->path, repo_dir, dmode, mkdir_flags); } else if (!external_tpl) { const char *content = tpl->content; From 3ac1ff42d7cfa8ac981a37712863419c071f2640 Mon Sep 17 00:00:00 2001 From: "Cha, Hojeong" Date: Tue, 27 May 2014 23:32:38 +0900 Subject: [PATCH 65/95] Fix compile error on Visual Studio --- tests/diff/workdir.c | 4 ++-- tests/repo/pathspec.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/diff/workdir.c b/tests/diff/workdir.c index f82bb00e8..963be9481 100644 --- a/tests/diff/workdir.c +++ b/tests/diff/workdir.c @@ -1610,8 +1610,8 @@ void test_diff_workdir__binary_detection(void) int i; git_buf data[10] = { { "1234567890", 0, 0 }, /* 0 - all ascii text control */ - { "Åü†HøπΩ", 0, 0 }, /* 1 - UTF-8 multibyte text */ - { "\xEF\xBB\xBFÜ⤒ƒ8£€", 0, 0 }, /* 2 - UTF-8 with BOM */ + { "\xC3\x85\xC3\xBC\xE2\x80\xA0\x48\xC3\xB8\xCF\x80\xCE\xA9", 0, 0 }, /* 1 - UTF-8 multibyte text */ + { "\xEF\xBB\xBF\xC3\x9C\xE2\xA4\x92\xC6\x92\x38\xC2\xA3\xE2\x82\xAC", 0, 0 }, /* 2 - UTF-8 with BOM */ { STR999Z, 0, 1000 }, /* 3 - ASCII with NUL at 1000 */ { STR3999Z, 0, 4000 }, /* 4 - ASCII with NUL at 4000 */ { STR4000 STR3999Z "x", 0, 8001 }, /* 5 - ASCII with NUL at 8000 */ diff --git a/tests/repo/pathspec.c b/tests/repo/pathspec.c index 334066b67..5b86662bc 100644 --- a/tests/repo/pathspec.c +++ b/tests/repo/pathspec.c @@ -167,7 +167,7 @@ void test_repo_pathspec__workdir4(void) cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps)); cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m)); - cl_assert_equal_s("这", git_pathspec_match_list_entry(m, 12)); + cl_assert_equal_s("\xE8\xBF\x99", git_pathspec_match_list_entry(m, 12)); git_pathspec_match_list_free(m); git_pathspec_free(ps); From d362093f9e858cf48d3c09bbcacf01f057b58db1 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 8 May 2014 15:41:36 -0700 Subject: [PATCH 66/95] Introduce GIT_MERGE_CONFIG_* for merge.ff settings git_merge_analysis will now return GIT_MERGE_CONFIG_NO_FASTFORWARD when merge.ff=false and GIT_MERGE_CONFIG_FASTFORWARD_ONLY when merge.ff=true --- include/git2/merge.h | 12 ++++++++ src/merge.c | 54 +++++++++++++++++++++++++++------- tests/merge/workdir/analysis.c | 26 ++++++++++++++++ 3 files changed, 82 insertions(+), 10 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index 7915050b0..dc63ed588 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -266,6 +266,18 @@ typedef enum { * to simply set HEAD to the target commit(s). */ GIT_MERGE_ANALYSIS_UNBORN = (1 << 3), + + /** + * There is a `merge.ff=false` configuration setting, suggesting that + * the user does not want to allow a fast-forward merge. + */ + GIT_MERGE_CONFIG_NO_FASTFORWARD = (1 << 4), + + /** + * There is a `merge.ff=only` configuration setting, suggesting that + * the user only wants fast-forward merges. + */ + GIT_MERGE_CONFIG_FASTFORWARD_ONLY = (1 << 5), } git_merge_analysis_t; /** diff --git a/src/merge.c b/src/merge.c index 6a8e5874f..85b74483b 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,6 +2564,37 @@ done: return error; } +int analysis_config(git_merge_analysis_t *out, git_repository *repo) +{ + git_config *config; + const char *value; + int bool_value, error = 0; + + if ((error = git_repository_config(&config, repo)) < 0) + goto done; + + if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + + goto done; + } + + if (git_config_parse_bool(&bool_value, value) == 0) { + if (!bool_value) + *out |= GIT_MERGE_CONFIG_NO_FASTFORWARD; + } else { + if (strcasecmp(value, "only") == 0) + *out |= GIT_MERGE_CONFIG_FASTFORWARD_ONLY; + } + +done: + git_config_free(config); + return error; +} + int git_merge_analysis( git_merge_analysis_t *out, git_repository *repo, @@ -2575,33 +2606,36 @@ int git_merge_analysis( assert(out && repo && their_heads); - *out = GIT_MERGE_ANALYSIS_NONE; - - if (git_repository_head_unborn(repo)) { - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; - goto done; - } - if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "Can only merge a single branch"); error = -1; goto done; } + *out = GIT_MERGE_ANALYSIS_NONE; + + if ((error = analysis_config(out, repo)) < 0) + goto done; + + if (git_repository_head_unborn(repo)) { + *out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + goto done; + } + if ((error = merge_heads(&ancestor_head, &our_head, repo, their_heads, their_heads_len)) < 0) goto done; /* We're up-to-date if we're trying to merge our own common ancestor. */ if (ancestor_head && git_oid_equal(&ancestor_head->oid, &their_heads[0]->oid)) - *out = GIT_MERGE_ANALYSIS_UP_TO_DATE; + *out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; /* We're fastforwardable if we're our own common ancestor. */ else if (ancestor_head && git_oid_equal(&ancestor_head->oid, &our_head->oid)) - *out = GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + *out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; /* Otherwise, just a normal merge is possible. */ else - *out = GIT_MERGE_ANALYSIS_NORMAL; + *out |= GIT_MERGE_ANALYSIS_NORMAL; done: git_merge_head_free(ancestor_head); diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c index 0e937857f..daa4e9dd8 100644 --- a/tests/merge/workdir/analysis.c +++ b/tests/merge/workdir/analysis.c @@ -105,3 +105,29 @@ void test_merge_workdir_analysis__unborn(void) git_buf_free(&master); } +void test_merge_workdir_analysis__fastforward_with_config_noff(void) +{ + git_config *config; + git_merge_analysis_t analysis; + + git_repository_config(&config, repo); + git_config_set_string(config, "merge.ff", "false"); + + analysis = analysis_from_branch(FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_CONFIG_NO_FASTFORWARD, (analysis & GIT_MERGE_CONFIG_NO_FASTFORWARD)); +} + +void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void) +{ + git_config *config; + git_merge_analysis_t analysis; + + git_repository_config(&config, repo); + git_config_set_string(config, "merge.ff", "only"); + + analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_CONFIG_FASTFORWARD_ONLY, (analysis & GIT_MERGE_CONFIG_FASTFORWARD_ONLY)); +} From a3622ba6cc0970abe485baa98ab53e32df28cfdc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 16 May 2014 13:54:40 -0500 Subject: [PATCH 67/95] Move GIT_MERGE_CONFIG_* to its own enum --- include/git2/merge.h | 14 +++++-- src/merge.c | 21 ++++++----- tests/merge/workdir/analysis.c | 69 +++++++++++++++++++--------------- 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index dc63ed588..d0092e45e 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -266,19 +266,26 @@ typedef enum { * to simply set HEAD to the target commit(s). */ GIT_MERGE_ANALYSIS_UNBORN = (1 << 3), +} git_merge_analysis_t; + +typedef enum { + /* + * No configuration was found that suggests a behavior for merge. + */ + GIT_MERGE_CONFIG_NONE = 0, /** * There is a `merge.ff=false` configuration setting, suggesting that * the user does not want to allow a fast-forward merge. */ - GIT_MERGE_CONFIG_NO_FASTFORWARD = (1 << 4), + GIT_MERGE_CONFIG_NO_FASTFORWARD = (1 << 0), /** * There is a `merge.ff=only` configuration setting, suggesting that * the user only wants fast-forward merges. */ - GIT_MERGE_CONFIG_FASTFORWARD_ONLY = (1 << 5), -} git_merge_analysis_t; + GIT_MERGE_CONFIG_FASTFORWARD_ONLY = (1 << 1), +} git_merge_config_t; /** * Analyzes the given branch(es) and determines the opportunities for @@ -292,6 +299,7 @@ typedef enum { */ GIT_EXTERN(int) git_merge_analysis( git_merge_analysis_t *analysis_out, + git_merge_config_t *config_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len); diff --git a/src/merge.c b/src/merge.c index 85b74483b..02851e526 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,12 +2564,14 @@ done: return error; } -int analysis_config(git_merge_analysis_t *out, git_repository *repo) +int merge_config(git_merge_config_t *out, git_repository *repo) { git_config *config; const char *value; int bool_value, error = 0; + *out = GIT_MERGE_CONFIG_NONE; + if ((error = git_repository_config(&config, repo)) < 0) goto done; @@ -2596,7 +2598,8 @@ done: } int git_merge_analysis( - git_merge_analysis_t *out, + git_merge_analysis_t *analysis_out, + git_merge_config_t *config_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len) @@ -2604,7 +2607,7 @@ int git_merge_analysis( git_merge_head *ancestor_head = NULL, *our_head = NULL; int error = 0; - assert(out && repo && their_heads); + assert(analysis_out && config_out && repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "Can only merge a single branch"); @@ -2612,13 +2615,13 @@ int git_merge_analysis( goto done; } - *out = GIT_MERGE_ANALYSIS_NONE; + *analysis_out = GIT_MERGE_ANALYSIS_NONE; - if ((error = analysis_config(out, repo)) < 0) + if ((error = merge_config(config_out, repo)) < 0) goto done; if (git_repository_head_unborn(repo)) { - *out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_UNBORN; goto done; } @@ -2627,15 +2630,15 @@ int git_merge_analysis( /* We're up-to-date if we're trying to merge our own common ancestor. */ if (ancestor_head && git_oid_equal(&ancestor_head->oid, &their_heads[0]->oid)) - *out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; + *analysis_out |= GIT_MERGE_ANALYSIS_UP_TO_DATE; /* We're fastforwardable if we're our own common ancestor. */ else if (ancestor_head && git_oid_equal(&ancestor_head->oid, &our_head->oid)) - *out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_FASTFORWARD | GIT_MERGE_ANALYSIS_NORMAL; /* Otherwise, just a normal merge is possible. */ else - *out |= GIT_MERGE_ANALYSIS_NORMAL; + *analysis_out |= GIT_MERGE_ANALYSIS_NORMAL; done: git_merge_head_free(ancestor_head); diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c index daa4e9dd8..d4cf587c4 100644 --- a/tests/merge/workdir/analysis.c +++ b/tests/merge/workdir/analysis.c @@ -36,71 +36,76 @@ void test_merge_workdir_analysis__cleanup(void) cl_git_sandbox_cleanup(); } -static git_merge_analysis_t analysis_from_branch(const char *branchname) +static void analysis_from_branch( + git_merge_analysis_t *merge_analysis, + git_merge_config_t *merge_config, + const char *branchname) { git_buf refname = GIT_BUF_INIT; git_reference *their_ref; git_merge_head *their_head; - git_merge_analysis_t analysis; git_buf_printf(&refname, "%s%s", GIT_REFS_HEADS_DIR, branchname); cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&refname))); cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref)); - cl_git_pass(git_merge_analysis(&analysis, repo, (const git_merge_head **)&their_head, 1)); + cl_git_pass(git_merge_analysis(merge_analysis, merge_config, repo, (const git_merge_head **)&their_head, 1)); git_buf_free(&refname); git_merge_head_free(their_head); git_reference_free(their_ref); - - return analysis; } void test_merge_workdir_analysis__fastforward(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; - analysis = analysis_from_branch(FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); + analysis_from_branch(&merge_analysis, &merge_config, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); } void test_merge_workdir_analysis__no_fastforward(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; - analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, analysis); + analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); } void test_merge_workdir_analysis__uptodate(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; - analysis = analysis_from_branch(UPTODATE_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, analysis); + analysis_from_branch(&merge_analysis, &merge_config, UPTODATE_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__uptodate_merging_prev_commit(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; - analysis = analysis_from_branch(PREVIOUS_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, analysis); + analysis_from_branch(&merge_analysis, &merge_config, PREVIOUS_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__unborn(void) { - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; git_buf master = GIT_BUF_INIT; git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master"); p_unlink(git_buf_cstr(&master)); - analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (analysis & GIT_MERGE_ANALYSIS_UNBORN)); + analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (merge_analysis & GIT_MERGE_ANALYSIS_UNBORN)); git_buf_free(&master); } @@ -108,26 +113,28 @@ void test_merge_workdir_analysis__unborn(void) void test_merge_workdir_analysis__fastforward_with_config_noff(void) { git_config *config; - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; git_repository_config(&config, repo); git_config_set_string(config, "merge.ff", "false"); - analysis = analysis_from_branch(FASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); - cl_assert_equal_i(GIT_MERGE_CONFIG_NO_FASTFORWARD, (analysis & GIT_MERGE_CONFIG_NO_FASTFORWARD)); + analysis_from_branch(&merge_analysis, &merge_config, FASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_CONFIG_NO_FASTFORWARD, (merge_config & GIT_MERGE_CONFIG_NO_FASTFORWARD)); } void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void) { git_config *config; - git_merge_analysis_t analysis; + git_merge_analysis_t merge_analysis; + git_merge_config_t merge_config; git_repository_config(&config, repo); git_config_set_string(config, "merge.ff", "only"); - analysis = analysis_from_branch(NOFASTFORWARD_BRANCH); - cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (analysis & GIT_MERGE_ANALYSIS_NORMAL)); - cl_assert_equal_i(GIT_MERGE_CONFIG_FASTFORWARD_ONLY, (analysis & GIT_MERGE_CONFIG_FASTFORWARD_ONLY)); + analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); + cl_assert_equal_i(GIT_MERGE_CONFIG_FASTFORWARD_ONLY, (merge_config & GIT_MERGE_CONFIG_FASTFORWARD_ONLY)); } From 22ab8881787f00eec479e1f148a3846a67c9bcfe Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 20 May 2014 22:07:15 -0700 Subject: [PATCH 68/95] Use a config snapshot --- src/merge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merge.c b/src/merge.c index 02851e526..54c7660db 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2572,7 +2572,7 @@ int merge_config(git_merge_config_t *out, git_repository *repo) *out = GIT_MERGE_CONFIG_NONE; - if ((error = git_repository_config(&config, repo)) < 0) + if ((error = git_repository_config_snapshot(&config, repo)) < 0) goto done; if ((error = git_config_get_string(&value, config, "merge.ff")) < 0) { From de3f851ec49deba7b152c01c53aa329439d1f3f5 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 21 May 2014 09:44:05 -0700 Subject: [PATCH 69/95] Staticify `merge_config` --- src/merge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/merge.c b/src/merge.c index 54c7660db..e2f1e388d 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,7 +2564,7 @@ done: return error; } -int merge_config(git_merge_config_t *out, git_repository *repo) +static int merge_config(git_merge_config_t *out, git_repository *repo) { git_config *config; const char *value; From eff531e1034401b144b02ff2913a361669d04129 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 27 May 2014 20:58:20 -0500 Subject: [PATCH 70/95] Modify GIT_MERGE_CONFIG -> GIT_MERGE_PREFERENCE --- include/git2/merge.h | 13 ++++++------ src/merge.c | 14 ++++++------- tests/merge/workdir/analysis.c | 36 +++++++++++++++++----------------- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/include/git2/merge.h b/include/git2/merge.h index d0092e45e..9eb14ccb1 100644 --- a/include/git2/merge.h +++ b/include/git2/merge.h @@ -270,22 +270,23 @@ typedef enum { typedef enum { /* - * No configuration was found that suggests a behavior for merge. + * No configuration was found that suggests a preferred behavior for + * merge. */ - GIT_MERGE_CONFIG_NONE = 0, + GIT_MERGE_PREFERENCE_NONE = 0, /** * There is a `merge.ff=false` configuration setting, suggesting that * the user does not want to allow a fast-forward merge. */ - GIT_MERGE_CONFIG_NO_FASTFORWARD = (1 << 0), + GIT_MERGE_PREFERENCE_NO_FASTFORWARD = (1 << 0), /** * There is a `merge.ff=only` configuration setting, suggesting that * the user only wants fast-forward merges. */ - GIT_MERGE_CONFIG_FASTFORWARD_ONLY = (1 << 1), -} git_merge_config_t; + GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = (1 << 1), +} git_merge_preference_t; /** * Analyzes the given branch(es) and determines the opportunities for @@ -299,7 +300,7 @@ typedef enum { */ GIT_EXTERN(int) git_merge_analysis( git_merge_analysis_t *analysis_out, - git_merge_config_t *config_out, + git_merge_preference_t *preference_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len); diff --git a/src/merge.c b/src/merge.c index e2f1e388d..a279d31d4 100644 --- a/src/merge.c +++ b/src/merge.c @@ -2564,13 +2564,13 @@ done: return error; } -static int merge_config(git_merge_config_t *out, git_repository *repo) +static int merge_preference(git_merge_preference_t *out, git_repository *repo) { git_config *config; const char *value; int bool_value, error = 0; - *out = GIT_MERGE_CONFIG_NONE; + *out = GIT_MERGE_PREFERENCE_NONE; if ((error = git_repository_config_snapshot(&config, repo)) < 0) goto done; @@ -2586,10 +2586,10 @@ static int merge_config(git_merge_config_t *out, git_repository *repo) if (git_config_parse_bool(&bool_value, value) == 0) { if (!bool_value) - *out |= GIT_MERGE_CONFIG_NO_FASTFORWARD; + *out |= GIT_MERGE_PREFERENCE_NO_FASTFORWARD; } else { if (strcasecmp(value, "only") == 0) - *out |= GIT_MERGE_CONFIG_FASTFORWARD_ONLY; + *out |= GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY; } done: @@ -2599,7 +2599,7 @@ done: int git_merge_analysis( git_merge_analysis_t *analysis_out, - git_merge_config_t *config_out, + git_merge_preference_t *preference_out, git_repository *repo, const git_merge_head **their_heads, size_t their_heads_len) @@ -2607,7 +2607,7 @@ int git_merge_analysis( git_merge_head *ancestor_head = NULL, *our_head = NULL; int error = 0; - assert(analysis_out && config_out && repo && their_heads); + assert(analysis_out && preference_out && repo && their_heads); if (their_heads_len != 1) { giterr_set(GITERR_MERGE, "Can only merge a single branch"); @@ -2617,7 +2617,7 @@ int git_merge_analysis( *analysis_out = GIT_MERGE_ANALYSIS_NONE; - if ((error = merge_config(config_out, repo)) < 0) + if ((error = merge_preference(preference_out, repo)) < 0) goto done; if (git_repository_head_unborn(repo)) { diff --git a/tests/merge/workdir/analysis.c b/tests/merge/workdir/analysis.c index d4cf587c4..85918abe4 100644 --- a/tests/merge/workdir/analysis.c +++ b/tests/merge/workdir/analysis.c @@ -38,7 +38,7 @@ void test_merge_workdir_analysis__cleanup(void) static void analysis_from_branch( git_merge_analysis_t *merge_analysis, - git_merge_config_t *merge_config, + git_merge_preference_t *merge_pref, const char *branchname) { git_buf refname = GIT_BUF_INIT; @@ -50,7 +50,7 @@ static void analysis_from_branch( cl_git_pass(git_reference_lookup(&their_ref, repo, git_buf_cstr(&refname))); cl_git_pass(git_merge_head_from_ref(&their_head, repo, their_ref)); - cl_git_pass(git_merge_analysis(merge_analysis, merge_config, repo, (const git_merge_head **)&their_head, 1)); + cl_git_pass(git_merge_analysis(merge_analysis, merge_pref, repo, (const git_merge_head **)&their_head, 1)); git_buf_free(&refname); git_merge_head_free(their_head); @@ -60,9 +60,9 @@ static void analysis_from_branch( void test_merge_workdir_analysis__fastforward(void) { git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; - analysis_from_branch(&merge_analysis, &merge_config, FASTFORWARD_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); } @@ -70,40 +70,40 @@ void test_merge_workdir_analysis__fastforward(void) void test_merge_workdir_analysis__no_fastforward(void) { git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; - analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, merge_analysis); } void test_merge_workdir_analysis__uptodate(void) { git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; - analysis_from_branch(&merge_analysis, &merge_config, UPTODATE_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, UPTODATE_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__uptodate_merging_prev_commit(void) { git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; - analysis_from_branch(&merge_analysis, &merge_config, PREVIOUS_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, PREVIOUS_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_UP_TO_DATE, merge_analysis); } void test_merge_workdir_analysis__unborn(void) { git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; git_buf master = GIT_BUF_INIT; git_buf_joinpath(&master, git_repository_path(repo), "refs/heads/master"); p_unlink(git_buf_cstr(&master)); - analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); cl_assert_equal_i(GIT_MERGE_ANALYSIS_UNBORN, (merge_analysis & GIT_MERGE_ANALYSIS_UNBORN)); @@ -114,27 +114,27 @@ void test_merge_workdir_analysis__fastforward_with_config_noff(void) { git_config *config; git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; git_repository_config(&config, repo); git_config_set_string(config, "merge.ff", "false"); - analysis_from_branch(&merge_analysis, &merge_config, FASTFORWARD_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, FASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_FASTFORWARD, (merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); - cl_assert_equal_i(GIT_MERGE_CONFIG_NO_FASTFORWARD, (merge_config & GIT_MERGE_CONFIG_NO_FASTFORWARD)); + cl_assert_equal_i(GIT_MERGE_PREFERENCE_NO_FASTFORWARD, (merge_pref & GIT_MERGE_PREFERENCE_NO_FASTFORWARD)); } void test_merge_workdir_analysis__no_fastforward_with_config_ffonly(void) { git_config *config; git_merge_analysis_t merge_analysis; - git_merge_config_t merge_config; + git_merge_preference_t merge_pref; git_repository_config(&config, repo); git_config_set_string(config, "merge.ff", "only"); - analysis_from_branch(&merge_analysis, &merge_config, NOFASTFORWARD_BRANCH); + analysis_from_branch(&merge_analysis, &merge_pref, NOFASTFORWARD_BRANCH); cl_assert_equal_i(GIT_MERGE_ANALYSIS_NORMAL, (merge_analysis & GIT_MERGE_ANALYSIS_NORMAL)); - cl_assert_equal_i(GIT_MERGE_CONFIG_FASTFORWARD_ONLY, (merge_config & GIT_MERGE_CONFIG_FASTFORWARD_ONLY)); + cl_assert_equal_i(GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY, (merge_pref & GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY)); } From 4386d80be108102548d4ff52c875aedfa94e7412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sat, 21 Dec 2013 17:18:21 +0000 Subject: [PATCH 71/95] clone: perform a "local clone" when given a local path When git is given such a path, it will perform a "local clone", bypassing the git-aware protocol and simply copying over all objects that exist in the source. Copy this behaviour when given a local path. --- include/git2/clone.h | 25 ++++++++++ src/clone.c | 114 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 128 insertions(+), 11 deletions(-) diff --git a/include/git2/clone.h b/include/git2/clone.h index 985c04bf6..ceb1a53bb 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -123,6 +123,31 @@ GIT_EXTERN(int) git_clone_into( const char *branch, const git_signature *signature); +/** + * Perform a local clone into a repository + * + * A "local clone" bypasses any git-aware protocols and simply copies + * over the object database from the source repository. It is often + * faster than a git-aware clone, but no verification of the data is + * performed, and can copy over too much data. + * + * @param repo the repository to use + * @param remote the remote repository to clone from + * @param co_opts options to use during checkout + * @param branch the branch to checkout after the clone, pass NULL for the + * remote's default branch + * @param signature the identity used when updating the reflog + * @return 0 on success, any non-zero return value from a callback + * function, or a negative value to indicate an error (use + * `giterr_last` for a detailed error message) + */ +GIT_EXTERN(int) git_clone_local_into( + git_repository *repo, + git_remote *remote, + const git_checkout_options *co_opts, + const char *branch, + const git_signature *signature); + /** @} */ GIT_END_DECL #endif diff --git a/src/clone.c b/src/clone.c index 8381ec63c..b66ba6b4c 100644 --- a/src/clone.c +++ b/src/clone.c @@ -22,6 +22,7 @@ #include "refs.h" #include "path.h" #include "repository.h" +#include "odb.h" static int create_branch( git_reference **branch, @@ -280,6 +281,23 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } +static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature, const char *reflog_message) +{ + int error; + + if (branch) + error = update_head_to_branch(repo, git_remote_name(remote), branch, + signature, reflog_message); + /* Point HEAD to the same ref as the remote's head */ + else + error = update_head_to_remote(repo, remote, signature, reflog_message); + + if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) + error = git_checkout_head(repo, co_opts); + + return error; +} + int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) { int error; @@ -311,15 +329,7 @@ int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) goto cleanup; - if (branch) - error = update_head_to_branch(repo, git_remote_name(remote), branch, - signature, git_buf_cstr(&reflog_message)); - /* Point HEAD to the same ref as the remote's head */ - else - error = update_head_to_remote(repo, remote, signature, git_buf_cstr(&reflog_message)); - - if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) - error = git_checkout_head(repo, co_opts); + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); cleanup: git_remote_free(remote); @@ -362,8 +372,15 @@ int git_clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - error = git_clone_into( - repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); + if (git__prefixcmp(url, "file://")) { + error = git_clone_local_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, options.signature); + } else { + error = git_clone_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, options.signature); + } git_remote_free(origin); } @@ -390,3 +407,78 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version) opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); return 0; } + +static const char *repository_base(git_repository *repo) +{ + if (git_repository_is_bare(repo)) + return git_repository_path(repo); + + return git_repository_workdir(repo); +} + +int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +{ + int error, root; + git_repository *src; + git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; + git_buf reflog_message = GIT_BUF_INIT; + const char *url; + + assert(repo && remote && co_opts); + + if (!git_repository_is_empty(repo)) { + giterr_set(GITERR_INVALID, "the repository is not empty"); + return -1; + } + + /* + * Let's figure out what path we should use for the source + * repo, if it's not rooted, the path should be relative to + * the repository's worktree/gitdir. + */ + url = git_remote_url(remote); + if (!git__prefixcmp(url, "file://")) + root = strlen("file://"); + else + root = git_path_root(url); + + if (root >= 0) + git_buf_puts(&src_path, url + root); + else + git_buf_joinpath(&src_path, repository_base(repo), url); + + if (git_buf_oom(&src_path)) + return -1; + + /* Copy .git/objects/ from the source to the target */ + if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) { + git_buf_free(&src_path); + return error; + } + + git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR); + git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR); + if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) { + error = -1; + goto cleanup; + } + + if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb), + 0, GIT_OBJECT_DIR_MODE)) < 0) + goto cleanup; + + git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); + + if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0) + goto cleanup; + + error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message)); + +cleanup: + git_buf_free(&reflog_message); + git_buf_free(&src_path); + git_buf_free(&src_odb); + git_buf_free(&dst_odb); + git_repository_free(src); + return error; +} From a0b5f7854c2302105e1029933df5d94a765cb89f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 22 Dec 2013 15:39:54 +0000 Subject: [PATCH 72/95] clone: store the realpath when given a relative one A call like git_clone("./foo", "./foo1") writes origin's url as './foo', which makes it unusable, as they're relative to different things. Go with git's behaviour and store the realpath as the url. --- src/clone.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/clone.c b/src/clone.c index b66ba6b4c..b6f8c7e85 100644 --- a/src/clone.c +++ b/src/clone.c @@ -242,6 +242,15 @@ static int create_and_configure_origin( int error; git_remote *origin = NULL; const char *name; + char buf[GIT_PATH_MAX]; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } name = options->remote_name ? options->remote_name : "origin"; if ((error = git_remote_create(&origin, repo, name, url)) < 0) @@ -372,7 +381,7 @@ int git_clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - if (git__prefixcmp(url, "file://")) { + if (git_path_exists(url) && git_path_isdir(url)) { error = git_clone_local_into( repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); From 121b26738e6a5e6eadeb2a2bc666956ae21cb92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 23 Dec 2013 11:12:31 +0000 Subject: [PATCH 73/95] clone: add flags to override whether to perform a local clone --- include/git2/clone.h | 7 +++++++ src/clone.c | 25 ++++++++++++++++++++++++- src/clone.h | 12 ++++++++++++ tests/clone/local.c | 29 +++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/clone.h create mode 100644 tests/clone/local.c diff --git a/include/git2/clone.h b/include/git2/clone.h index ceb1a53bb..71e8e72f2 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -23,6 +23,12 @@ */ GIT_BEGIN_DECL +typedef enum { + GIT_CLONE_LOCAL_AUTO, + GIT_CLONE_LOCAL, + GIT_CLONE_NO_LOCAL, +} git_clone_local_t; + /** * Clone options structure * @@ -57,6 +63,7 @@ typedef struct git_clone_options { int bare; int ignore_cert_errors; + git_clone_local_t local; const char *remote_name; const char* checkout_branch; git_signature *signature; diff --git a/src/clone.c b/src/clone.c index b6f8c7e85..58430f63a 100644 --- a/src/clone.c +++ b/src/clone.c @@ -347,6 +347,29 @@ cleanup: return error; } +int git_clone__should_clone_local(const char *url, git_clone_local_t local) +{ + const char *path; + int is_url; + + if (local == GIT_CLONE_NO_LOCAL) + return false; + + is_url = !git__prefixcmp(url, "file://"); + + if (is_url && local != GIT_CLONE_LOCAL) + return false; + + path = url; + if (is_url) + path = url + strlen("file://"); + + if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) + return true; + + return false; +} + int git_clone( git_repository **out, const char *url, @@ -381,7 +404,7 @@ int git_clone( return error; if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { - if (git_path_exists(url) && git_path_isdir(url)) { + if (git_clone__should_clone_local(url, options.local)) { error = git_clone_local_into( repo, origin, &options.checkout_opts, options.checkout_branch, options.signature); diff --git a/src/clone.h b/src/clone.h new file mode 100644 index 000000000..14ca5d44c --- /dev/null +++ b/src/clone.h @@ -0,0 +1,12 @@ +/* + * 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_clone_h__ +#define INCLUDE_clone_h__ + +extern int git_clone__should_clone_local(const char *url, git_clone_local_t local); + +#endif diff --git a/tests/clone/local.c b/tests/clone/local.c new file mode 100644 index 000000000..478bbb673 --- /dev/null +++ b/tests/clone/local.c @@ -0,0 +1,29 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "clone.h" +#include "buffer.h" + +void assert_clone(const char *path, git_clone_local_t opt, int val) +{ + cl_assert_equal_i(val, git_clone__should_clone_local(path, opt)); +} + +void test_clone_local__should_clone_local(void) +{ + git_buf buf = GIT_BUF_INIT; + const char *path; + + /* we use a fixture path because it needs to exist for us to want to clone */ + + cl_git_pass(git_buf_printf(&buf, "file://%s", cl_fixture("testrepo.git"))); + cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); + git_buf_free(&buf); + + path = cl_fixture("testrepo.git"); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); + cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); +} From c1dbfcbb4a5ca92ae90f1bdb7004edb2eb86284c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 28 May 2014 10:07:23 +0200 Subject: [PATCH 74/95] clone: add flag not to link --- include/git2/clone.h | 1 + src/clone.c | 2 +- tests/clone/local.c | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/include/git2/clone.h b/include/git2/clone.h index 71e8e72f2..31bb52ccd 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -27,6 +27,7 @@ typedef enum { GIT_CLONE_LOCAL_AUTO, GIT_CLONE_LOCAL, GIT_CLONE_NO_LOCAL, + GIT_CLONE_LOCAL_NO_LINKS, } git_clone_local_t; /** diff --git a/src/clone.c b/src/clone.c index 58430f63a..c02ca04b4 100644 --- a/src/clone.c +++ b/src/clone.c @@ -357,7 +357,7 @@ int git_clone__should_clone_local(const char *url, git_clone_local_t local) is_url = !git__prefixcmp(url, "file://"); - if (is_url && local != GIT_CLONE_LOCAL) + if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) return false; path = url; diff --git a/tests/clone/local.c b/tests/clone/local.c index 478bbb673..7b273b23a 100644 --- a/tests/clone/local.c +++ b/tests/clone/local.c @@ -19,11 +19,13 @@ void test_clone_local__should_clone_local(void) cl_git_pass(git_buf_printf(&buf, "file://%s", cl_fixture("testrepo.git"))); cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_AUTO)); cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, git_clone__should_clone_local(buf.ptr, GIT_CLONE_LOCAL_NO_LINKS)); cl_assert_equal_i(false, git_clone__should_clone_local(buf.ptr, GIT_CLONE_NO_LOCAL)); git_buf_free(&buf); path = cl_fixture("testrepo.git"); cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_AUTO)); cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL)); + cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); } From 94f742bac60656f4f915711b953814477633b984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 28 May 2014 10:18:05 +0200 Subject: [PATCH 75/95] fileops: allow linking files when copying directory structures When passed the LINK_FILES flag, the recursive copy will hardlink files instead of copying them. --- src/fileops.c | 6 ++++-- src/fileops.h | 2 ++ tests/core/copy.c | 26 ++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/fileops.c b/src/fileops.c index 13b8f6a39..bebbae4f9 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -740,9 +740,11 @@ static int _cp_r_callback(void *ref, git_buf *from) return error; /* make symlink or regular file */ - if (S_ISLNK(from_st.st_mode)) + if (info->flags & GIT_CPDIR_LINK_FILES) { + error = p_link(from->ptr, info->to.ptr); + } else if (S_ISLNK(from_st.st_mode)) { error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - else { + } else { mode_t usemode = from_st.st_mode; if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) diff --git a/src/fileops.h b/src/fileops.h index 62227abae..4f5700a99 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -173,6 +173,7 @@ extern int git_futils_cp( * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the * source file to the target; with this flag, always use 0666 (or 0777 if * source has exec bits set) for target. + * - GIT_CPDIR_LINK_FILES will try to use hardlinks for the files */ typedef enum { GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), @@ -181,6 +182,7 @@ typedef enum { GIT_CPDIR_OVERWRITE = (1u << 3), GIT_CPDIR_CHMOD_DIRS = (1u << 4), GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), + GIT_CPDIR_LINK_FILES = (1u << 6), } git_futils_cpdir_flags; /** diff --git a/tests/core/copy.c b/tests/core/copy.c index c0c59c056..04b2dfab5 100644 --- a/tests/core/copy.c +++ b/tests/core/copy.c @@ -45,6 +45,16 @@ void test_core_copy__file_in_dir(void) cl_assert(!git_path_isdir("an_dir")); } +void assert_hard_link(const char *path) +{ + /* we assert this by checking that there's more than one link to the file */ + struct stat st; + + cl_assert(git_path_isfile(path)); + cl_git_pass(p_stat(path, &st)); + cl_assert(st.st_nlink > 1); +} + void test_core_copy__tree(void) { struct stat st; @@ -122,5 +132,21 @@ void test_core_copy__tree(void) cl_git_pass(git_futils_rmdir_r("t2", NULL, GIT_RMDIR_REMOVE_FILES)); cl_assert(!git_path_isdir("t2")); +#ifndef GIT_WIN32 + cl_git_pass(git_futils_cp_r("src", "t3", GIT_CPDIR_CREATE_EMPTY_DIRS | GIT_CPDIR_LINK_FILES, 0)); + cl_assert(git_path_isdir("t3")); + + cl_assert(git_path_isdir("t3")); + cl_assert(git_path_isdir("t3/b")); + cl_assert(git_path_isdir("t3/c")); + cl_assert(git_path_isdir("t3/c/d")); + cl_assert(git_path_isdir("t3/c/e")); + + assert_hard_link("t3/f1"); + assert_hard_link("t3/b/f2"); + assert_hard_link("t3/c/f3"); + assert_hard_link("t3/c/d/f4"); +#endif + cl_git_pass(git_futils_rmdir_r("src", NULL, GIT_RMDIR_REMOVE_FILES)); } From 2614819cf3e2163fb831c12c6d793254c78adb3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Wed, 28 May 2014 11:28:57 +0200 Subject: [PATCH 76/95] clone: allow for linking in local clone If requested, git_clone_local_into() will try to link the object files instead of copying them. This only works on non-Windows (since it doesn't have this) when both are on the same filesystem (which are unix semantics). --- include/git2/clone.h | 4 +++ src/clone.c | 36 +++++++++++++++++++++++---- tests/clone/local.c | 59 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) diff --git a/include/git2/clone.h b/include/git2/clone.h index 31bb52ccd..b2c944a78 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -144,6 +144,9 @@ GIT_EXTERN(int) git_clone_into( * @param co_opts options to use during checkout * @param branch the branch to checkout after the clone, pass NULL for the * remote's default branch + * @param link wether to use hardlinks instead of copying + * objects. This is only possible if both repositories are on the same + * filesystem. * @param signature the identity used when updating the reflog * @return 0 on success, any non-zero return value from a callback * function, or a negative value to indicate an error (use @@ -154,6 +157,7 @@ GIT_EXTERN(int) git_clone_local_into( git_remote *remote, const git_checkout_options *co_opts, const char *branch, + int link, const git_signature *signature); /** @} */ diff --git a/src/clone.c b/src/clone.c index c02ca04b4..5aaa94ff6 100644 --- a/src/clone.c +++ b/src/clone.c @@ -405,9 +405,10 @@ int git_clone( if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { if (git_clone__should_clone_local(url, options.local)) { + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; error = git_clone_local_into( repo, origin, &options.checkout_opts, - options.checkout_branch, options.signature); + options.checkout_branch, link, options.signature); } else { error = git_clone_into( repo, origin, &options.checkout_opts, @@ -448,15 +449,36 @@ static const char *repository_base(git_repository *repo) return git_repository_workdir(repo); } -int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +static bool can_link(const char *src, const char *dst, int link) { - int error, root; +#ifdef GIT_WIN32 + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) +{ + int error, root, flags; git_repository *src; git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; git_buf reflog_message = GIT_BUF_INIT; const char *url; - assert(repo && remote && co_opts); + assert(repo && remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); @@ -495,8 +517,12 @@ int git_clone_local_into(git_repository *repo, git_remote *remote, const git_che goto cleanup; } + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb), - 0, GIT_OBJECT_DIR_MODE)) < 0) + flags, GIT_OBJECT_DIR_MODE)) < 0) goto cleanup; git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); diff --git a/tests/clone/local.c b/tests/clone/local.c index 7b273b23a..289c50aaf 100644 --- a/tests/clone/local.c +++ b/tests/clone/local.c @@ -3,6 +3,8 @@ #include "git2/clone.h" #include "clone.h" #include "buffer.h" +#include "path.h" +#include "posix.h" void assert_clone(const char *path, git_clone_local_t opt, int val) { @@ -29,3 +31,60 @@ void test_clone_local__should_clone_local(void) cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); } + +void test_clone_local__hardlinks(void) +{ + git_repository *repo; + git_remote *remote; + git_signature *sig; + git_buf buf = GIT_BUF_INIT; + struct stat st; + + cl_git_pass(git_repository_init(&repo, "./clone.git", true)); + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_fixture("testrepo.git"))); + cl_git_pass(git_signature_now(&sig, "foo", "bar")); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + + git_remote_free(remote); + git_repository_free(repo); + + /* + * We can't rely on the link option taking effect in the first + * clone, since the temp dir and fixtures dir may reside on + * different filesystems. We perform the second clone + * side-by-side to make sure this is the case. + */ + + cl_git_pass(git_repository_init(&repo, "./clone2.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + +#ifndef GIT_WIN32 + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert(st.st_nlink > 1); +#endif + + git_remote_free(remote); + git_repository_free(repo); + git_buf_clear(&buf); + + cl_git_pass(git_repository_init(&repo, "./clone3.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig)); + + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(1, st.st_nlink); + + git_buf_free(&buf); + git_signature_free(sig); + git_remote_free(remote); + git_repository_free(repo); +} From 33bf1b1ab0e14453e67e94dc6aa679dcdcce56e8 Mon Sep 17 00:00:00 2001 From: Eoin Coffey Date: Wed, 28 May 2014 09:40:08 -0600 Subject: [PATCH 77/95] examples/log.c: invert filtering impl and conditional --- examples/log.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/log.c b/examples/log.c index 965f854d7..d5f75a297 100644 --- a/examples/log.c +++ b/examples/log.c @@ -67,8 +67,8 @@ static void print_commit(git_commit *commit); static int match_with_parent(git_commit *commit, int i, git_diff_options *); /** utility functions for filtering */ -static int signature_does_not_match(const git_signature *sig, const char *filter); -static int log_message_does_not_match(const git_commit *commit, const char *filter); +static int signature_matches(const git_signature *sig, const char *filter); +static int log_message_matches(const git_commit *commit, const char *filter); int main(int argc, char *argv[]) { @@ -132,13 +132,13 @@ int main(int argc, char *argv[]) continue; } - if (signature_does_not_match(git_commit_author(commit), opt.author)) + if (!signature_matches(git_commit_author(commit), opt.author)) continue; - if (signature_does_not_match(git_commit_committer(commit), opt.committer)) + if (!signature_matches(git_commit_committer(commit), opt.committer)) continue; - if (log_message_does_not_match(commit, opt.grep)) + if (!log_message_matches(commit, opt.grep)) continue; if (count++ < opt.skip) @@ -186,26 +186,26 @@ int main(int argc, char *argv[]) } /** Determine if the given git_signature does not contain the filter text. */ -static int signature_does_not_match(const git_signature *sig, const char *filter) { +static int signature_matches(const git_signature *sig, const char *filter) { if (filter == NULL) - return 0; + return 1; - if (sig == NULL || - (strstr(sig->name, filter) == NULL && - strstr(sig->email, filter) == NULL)) + if (sig != NULL && + (strstr(sig->name, filter) != NULL || + strstr(sig->email, filter) != NULL)) return 1; return 0; } -static int log_message_does_not_match(const git_commit *commit, const char *filter) { +static int log_message_matches(const git_commit *commit, const char *filter) { const char *message = NULL; if (filter == NULL) - return 0; + return 1; - if ((message = git_commit_message(commit)) == NULL || - strstr(message, filter) == NULL) + if ((message = git_commit_message(commit)) != NULL && + strstr(message, filter) != NULL) return 1; return 0; From fda73bc5fd9f118c661e963f5c50f5c87df2364c Mon Sep 17 00:00:00 2001 From: Ungureanu Marius Date: Wed, 28 May 2014 22:57:21 +0300 Subject: [PATCH 78/95] [Blob] Update documentation for is_binary. filter.h tells me that we check the first 8000 bytes. --- include/git2/blob.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/git2/blob.h b/include/git2/blob.h index 1b6583309..c24ff7e7f 100644 --- a/include/git2/blob.h +++ b/include/git2/blob.h @@ -210,7 +210,7 @@ GIT_EXTERN(int) git_blob_create_frombuffer( * * The heuristic used to guess if a file is binary is taken from core git: * Searching for NUL bytes and looking for a reasonable ratio of printable - * to non-printable characters among the first 4000 bytes. + * to non-printable characters among the first 8000 bytes. * * @param blob The blob which content should be analyzed * @return 1 if the content of the blob is detected From bc9f67fa8524a8bcf343af6a721f57b52334810b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Thu, 29 May 2014 10:03:04 +0200 Subject: [PATCH 79/95] clone: more explicit local tests Assert the exact amount of links we expect. While there, check that a plain git_clone() automatically chooses to link. --- tests/clone/local.c | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/tests/clone/local.c b/tests/clone/local.c index 289c50aaf..a4406c1cc 100644 --- a/tests/clone/local.c +++ b/tests/clone/local.c @@ -5,11 +5,7 @@ #include "buffer.h" #include "path.h" #include "posix.h" - -void assert_clone(const char *path, git_clone_local_t opt, int val) -{ - cl_assert_equal_i(val, git_clone__should_clone_local(path, opt)); -} +#include "fileops.h" void test_clone_local__should_clone_local(void) { @@ -40,20 +36,21 @@ void test_clone_local__hardlinks(void) git_buf buf = GIT_BUF_INIT; struct stat st; + + /* + * In this first clone, we just copy over, since the temp dir + * will often be in a different filesystem, so we cannot + * link. It also allows us to control the number of links + */ cl_git_pass(git_repository_init(&repo, "./clone.git", true)); cl_git_pass(git_remote_create(&remote, repo, "origin", cl_fixture("testrepo.git"))); cl_git_pass(git_signature_now(&sig, "foo", "bar")); - cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig)); git_remote_free(remote); git_repository_free(repo); - /* - * We can't rely on the link option taking effect in the first - * clone, since the temp dir and fixtures dir may reside on - * different filesystems. We perform the second clone - * side-by-side to make sure this is the case. - */ + /* This second clone is in the same filesystem, so we can hardlink */ cl_git_pass(git_repository_init(&repo, "./clone2.git", true)); cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); @@ -65,7 +62,7 @@ void test_clone_local__hardlinks(void) cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); cl_git_pass(p_stat(buf.ptr, &st)); - cl_assert(st.st_nlink > 1); + cl_assert_equal_i(2, st.st_nlink); #endif git_remote_free(remote); @@ -83,8 +80,26 @@ void test_clone_local__hardlinks(void) cl_git_pass(p_stat(buf.ptr, &st)); cl_assert_equal_i(1, st.st_nlink); - git_buf_free(&buf); - git_signature_free(sig); git_remote_free(remote); git_repository_free(repo); + + /* this one should automatically use links */ + cl_git_pass(git_clone(&repo, "./clone.git", "./clone4.git", NULL)); + +#ifndef GIT_WIN32 + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(3, st.st_nlink); +#endif + + git_buf_free(&buf); + git_signature_free(sig); + git_repository_free(repo); + + cl_git_pass(git_futils_rmdir_r("./clone.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone2.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone3.git", NULL, GIT_RMDIR_REMOVE_FILES)); + cl_git_pass(git_futils_rmdir_r("./clone4.git", NULL, GIT_RMDIR_REMOVE_FILES)); } From 5f0527aeac65b10b0df9034f5763865d253daf75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Fri, 30 May 2014 13:06:34 +0200 Subject: [PATCH 80/95] config: initialize the error The error would be uninitialized if we take a snapshot of a config with no backends. --- src/config.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 4bd27a875..8a0fb653c 100644 --- a/src/config.c +++ b/src/config.c @@ -139,7 +139,7 @@ int git_config_open_ondisk(git_config **out, const char *path) int git_config_snapshot(git_config **out, git_config *in) { - int error; + int error = 0; size_t i; file_internal *internal; git_config *config; From 68f9d6b2833774c279964790eda97363225e09a7 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 15 May 2014 22:44:50 +0200 Subject: [PATCH 81/95] Refs: Fix some issue when core.precomposeunicode = true. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes two issues I found when core.precomposeunicode is enabled: * When creating a reference with a NFD string, the returned git_reference would return this NFD string as the reference’s name. But when looking up the reference later, the name would then be returned as NFC string. * Renaming a reference would not honor the core.precomposeunicode and apply no normalization to the new reference name. --- src/refs.c | 33 +++++++++------------------------ src/refs.h | 1 - 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/refs.c b/src/refs.c index 9428f617d..adbabb452 100644 --- a/src/refs.c +++ b/src/refs.c @@ -365,7 +365,7 @@ static int reference__create( if (ref_out) *ref_out = NULL; - error = git_reference__normalize_name_lax(normalized, sizeof(normalized), name); + error = reference_normalize_for_repo(normalized, sizeof(normalized), repo, name); if (error < 0) return error; @@ -388,15 +388,15 @@ static int reference__create( return -1; } - ref = git_reference__alloc(name, oid, NULL); + ref = git_reference__alloc(normalized, oid, NULL); } else { char normalized_target[GIT_REFNAME_MAX]; - if ((error = git_reference__normalize_name_lax( - normalized_target, sizeof(normalized_target), symbolic)) < 0) + if ((error = reference_normalize_for_repo( + normalized_target, sizeof(normalized_target), repo, symbolic)) < 0) return error; - ref = git_reference__alloc_symbolic(name, normalized_target); + ref = git_reference__alloc_symbolic(normalized, normalized_target); } GITERR_CHECK_ALLOC(ref); @@ -569,18 +569,14 @@ int git_reference_symbolic_set_target( static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, const git_signature *signature, const char *message) { - unsigned int normalization_flags; char normalized[GIT_REFNAME_MAX]; bool should_head_be_updated = false; int error = 0; assert(ref && new_name && signature); - normalization_flags = ref->type == GIT_REF_SYMBOLIC ? - GIT_REF_FORMAT_ALLOW_ONELEVEL : GIT_REF_FORMAT_NORMAL; - - if ((error = git_reference_normalize_name( - normalized, sizeof(normalized), new_name, normalization_flags)) < 0) + if ((error = reference_normalize_for_repo( + normalized, sizeof(normalized), git_reference_owner(ref), new_name)) < 0) return error; @@ -590,12 +586,12 @@ static int reference__rename(git_reference **out, git_reference *ref, const char should_head_be_updated = (error > 0); - if ((error = git_refdb_rename(out, ref->db, ref->name, new_name, force, signature, message)) < 0) + if ((error = git_refdb_rename(out, ref->db, ref->name, normalized, force, signature, message)) < 0) return error; /* Update HEAD it was pointing to the reference being renamed */ if (should_head_be_updated && - (error = git_repository_set_head(ref->db->repo, new_name, signature, message)) < 0) { + (error = git_repository_set_head(ref->db->repo, normalized, signature, message)) < 0) { giterr_set(GITERR_REFERENCE, "Failed to update HEAD after renaming reference"); return error; } @@ -1018,17 +1014,6 @@ cleanup: return error; } -int git_reference__normalize_name_lax( - char *buffer_out, - size_t out_size, - const char *name) -{ - return git_reference_normalize_name( - buffer_out, - out_size, - name, - GIT_REF_FORMAT_ALLOW_ONELEVEL); -} #define GIT_REF_TYPEMASK (GIT_REF_OID | GIT_REF_SYMBOLIC) int git_reference_cmp( diff --git a/src/refs.h b/src/refs.h index d57d67026..7337e2a48 100644 --- a/src/refs.h +++ b/src/refs.h @@ -66,7 +66,6 @@ struct git_reference { git_reference *git_reference__set_name(git_reference *ref, const char *name); -int git_reference__normalize_name_lax(char *buffer_out, size_t out_size, const char *name); int git_reference__normalize_name(git_buf *buf, const char *name, unsigned int flags); int git_reference__update_terminal(git_repository *repo, const char *ref_name, const git_oid *oid, const git_signature *signature, const char *log_message); int git_reference__is_valid_name(const char *refname, unsigned int flags); From 824f755f101525d60adb7a73b407880c4aaec950 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 20 May 2014 17:31:53 +0200 Subject: [PATCH 82/95] Refs: Introduce `git_refname_t`. --- src/refs.c | 23 ++++++++++------------- src/refs.h | 2 ++ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/refs.c b/src/refs.c index adbabb452..1603876da 100644 --- a/src/refs.c +++ b/src/refs.c @@ -159,8 +159,7 @@ int git_reference_name_to_id( } static int reference_normalize_for_repo( - char *out, - size_t out_size, + git_refname_t out, git_repository *repo, const char *name) { @@ -171,7 +170,7 @@ static int reference_normalize_for_repo( precompose) flags |= GIT_REF_FORMAT__PRECOMPOSE_UNICODE; - return git_reference_normalize_name(out, out_size, name, flags); + return git_reference_normalize_name(out, GIT_REFNAME_MAX, name, flags); } int git_reference_lookup_resolved( @@ -180,7 +179,7 @@ int git_reference_lookup_resolved( const char *name, int max_nesting) { - char scan_name[GIT_REFNAME_MAX]; + git_refname_t scan_name; git_ref_t scan_type; int error = 0, nesting; git_reference *ref = NULL; @@ -197,8 +196,7 @@ int git_reference_lookup_resolved( scan_type = GIT_REF_SYMBOLIC; - if ((error = reference_normalize_for_repo( - scan_name, sizeof(scan_name), repo, name)) < 0) + if ((error = reference_normalize_for_repo(scan_name, repo, name)) < 0) return error; if ((error = git_repository_refdb__weakptr(&refdb, repo)) < 0) @@ -354,7 +352,7 @@ static int reference__create( const git_oid *old_id, const char *old_target) { - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; git_refdb *refdb; git_reference *ref = NULL; int error = 0; @@ -365,7 +363,7 @@ static int reference__create( if (ref_out) *ref_out = NULL; - error = reference_normalize_for_repo(normalized, sizeof(normalized), repo, name); + error = reference_normalize_for_repo(normalized, repo, name); if (error < 0) return error; @@ -390,10 +388,9 @@ static int reference__create( ref = git_reference__alloc(normalized, oid, NULL); } else { - char normalized_target[GIT_REFNAME_MAX]; + git_refname_t normalized_target; - if ((error = reference_normalize_for_repo( - normalized_target, sizeof(normalized_target), repo, symbolic)) < 0) + if ((error = reference_normalize_for_repo(normalized_target, repo, symbolic)) < 0) return error; ref = git_reference__alloc_symbolic(normalized, normalized_target); @@ -569,14 +566,14 @@ int git_reference_symbolic_set_target( static int reference__rename(git_reference **out, git_reference *ref, const char *new_name, int force, const git_signature *signature, const char *message) { - char normalized[GIT_REFNAME_MAX]; + git_refname_t normalized; bool should_head_be_updated = false; int error = 0; assert(ref && new_name && signature); if ((error = reference_normalize_for_repo( - normalized, sizeof(normalized), git_reference_owner(ref), new_name)) < 0) + normalized, git_reference_owner(ref), new_name)) < 0) return error; diff --git a/src/refs.h b/src/refs.h index 7337e2a48..f75a4bf7e 100644 --- a/src/refs.h +++ b/src/refs.h @@ -51,6 +51,8 @@ #define GIT_REFNAME_MAX 1024 +typedef char git_refname_t[GIT_REFNAME_MAX]; + struct git_reference { git_refdb *db; git_ref_t type; From 1a90b1e3f106139f75183ef21dd5b461d9d83f1d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 30 May 2014 14:53:28 +0200 Subject: [PATCH 83/95] Refs: Add a unicode test for git_branch_move. This tests that decomposed branch names are correctly precomposed when passed to `git_branch_move` and `core.precomposeunicode` is enabled. --- tests/refs/branches/move.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/refs/branches/move.c b/tests/refs/branches/move.c index 6c6dbbe21..f136b00d6 100644 --- a/tests/refs/branches/move.c +++ b/tests/refs/branches/move.c @@ -241,3 +241,20 @@ void test_refs_branches_move__default_reflog_message(void) git_reflog_free(log); git_signature_free(sig); } + +void test_refs_branches_move__can_move_with_unicode(void) +{ + git_reference *original_ref, *new_ref; + const char *new_branch_name = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D"; + + cl_git_pass(git_reference_lookup(&original_ref, repo, "refs/heads/br2")); + cl_git_pass(git_branch_move(&new_ref, original_ref, new_branch_name, 0, NULL, NULL)); + + if (cl_repo_get_bool(repo, "core.precomposeunicode")) + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\xC3\x85\x73\x74\x72\xC3\xB6\x6D", git_reference_name(new_ref)); + else + cl_assert_equal_s(GIT_REFS_HEADS_DIR "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D", git_reference_name(new_ref)); + + git_reference_free(original_ref); + git_reference_free(new_ref); +} From 9d6c3d2853901f2fba049ba80cadb71caa8535c1 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 30 May 2014 15:15:54 +0200 Subject: [PATCH 84/95] Refs: Extend unicode test for branch creation. This adds another assertion to ensure that the reference name inside the git_reference struct returned by `git_branch_create` is returned as precomposed if `core.precomposeunicode` is enabled. --- tests/refs/branches/create.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/refs/branches/create.c b/tests/refs/branches/create.c index 864640ab3..3a4f33b6e 100644 --- a/tests/refs/branches/create.c +++ b/tests/refs/branches/create.c @@ -179,11 +179,14 @@ void test_refs_branches_create__can_create_branch_with_unicode(void) expected[0] = nfd; for (i = 0; i < ARRAY_SIZE(names); ++i) { + const char *name; cl_git_pass(git_branch_create( &branch, repo, names[i], target, 0, NULL, NULL)); cl_git_pass(git_oid_cmp( git_reference_target(branch), git_commit_id(target))); + cl_git_pass(git_branch_name(&name, branch)); + cl_assert_equal_s(expected[i], name); assert_branch_matches_name(expected[i], names[i]); if (fs_decompose_unicode && alt[i]) assert_branch_matches_name(expected[i], alt[i]); From 49837fd49fb9de999be5add82a12bf6332d4703f Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Fri, 30 May 2014 11:30:53 -0500 Subject: [PATCH 85/95] Ignore core.safecrlf=warn until we have a warn infrastructure --- src/config_cache.c | 8 +++++++- tests/filter/crlf.c | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/config_cache.c b/src/config_cache.c index dca9976f8..45c39ce17 100644 --- a/src/config_cache.c +++ b/src/config_cache.c @@ -51,6 +51,12 @@ static git_cvar_map _cvar_map_autocrlf[] = { {GIT_CVAR_STRING, "input", GIT_AUTO_CRLF_INPUT} }; +static git_cvar_map _cvar_map_safecrlf[] = { + {GIT_CVAR_FALSE, NULL, GIT_SAFE_CRLF_FALSE}, + {GIT_CVAR_TRUE, NULL, GIT_SAFE_CRLF_FAIL}, + {GIT_CVAR_STRING, "warn", GIT_SAFE_CRLF_WARN} +}; + /* * Generic map for integer values */ @@ -68,7 +74,7 @@ static struct map_data _cvar_maps[] = { {"core.trustctime", NULL, 0, GIT_TRUSTCTIME_DEFAULT }, {"core.abbrev", _cvar_map_int, 1, GIT_ABBREV_DEFAULT }, {"core.precomposeunicode", NULL, 0, GIT_PRECOMPOSE_DEFAULT }, - {"core.safecrlf", NULL, 0, GIT_SAFE_CRLF_DEFAULT}, + {"core.safecrlf", _cvar_map_safecrlf, ARRAY_SIZE(_cvar_map_safecrlf), GIT_SAFE_CRLF_DEFAULT}, {"core.logallrefupdates", NULL, 0, GIT_LOGALLREFUPDATES_DEFAULT }, }; diff --git a/tests/filter/crlf.c b/tests/filter/crlf.c index 334b1e349..a31dac965 100644 --- a/tests/filter/crlf.c +++ b/tests/filter/crlf.c @@ -196,3 +196,44 @@ void test_filter_crlf__no_safecrlf(void) git_buf_free(&out); } +void test_filter_crlf__safecrlf_warn(void) +{ + git_filter_list *fl; + git_filter *crlf; + git_buf in = {0}, out = GIT_BUF_INIT; + + cl_repo_set_string(g_repo, "core.safecrlf", "warn"); + + cl_git_pass(git_filter_list_new( + &fl, g_repo, GIT_FILTER_TO_ODB, 0)); + + crlf = git_filter_lookup(GIT_FILTER_CRLF); + cl_assert(crlf != NULL); + + cl_git_pass(git_filter_list_push(fl, crlf, NULL)); + + /* Normalized \r\n succeeds with safecrlf=warn */ + in.ptr = "Normal\r\nCRLF\r\nline-endings.\r\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s("Normal\nCRLF\nline-endings.\n", out.ptr); + + /* Mix of line endings succeeds with safecrlf=warn */ + in.ptr = "Mixed\nup\r\nLF\nand\r\nCRLF\nline-endings.\r\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + /* TODO: check for warning */ + cl_assert_equal_s("Mixed\nup\nLF\nand\nCRLF\nline-endings.\n", out.ptr); + + /* Normalized \n is reversible, so does not fail with safecrlf=warn */ + in.ptr = "Normal\nLF\nonly\nline-endings.\n"; + in.size = strlen(in.ptr); + + cl_git_pass(git_filter_list_apply_to_data(&out, fl, &in)); + cl_assert_equal_s(in.ptr, out.ptr); + + git_filter_list_free(fl); + git_buf_free(&out); +} From d723dbed0c46ddb2fb037c63cc13a6131c3824b8 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Fri, 30 May 2014 19:26:49 +0200 Subject: [PATCH 86/95] Remote: Set an error when a remote cannot be found. Inside `git_remote_load`, the calls to `get_optional_config` use `giterr_clear` to unset any errors that are set due to missing config keys. If neither a fetch nor a push url config was found for a remote, we should set an error again. --- src/remote.c | 1 + tests/network/remote/remotes.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/remote.c b/src/remote.c index f2e2e2f7a..abcf55e3d 100644 --- a/src/remote.c +++ b/src/remote.c @@ -403,6 +403,7 @@ int git_remote_load(git_remote **out, git_repository *repo, const char *name) if (!optional_setting_found) { error = GIT_ENOTFOUND; + giterr_set(GITERR_CONFIG, "Remote '%s' does not exist.", name); goto cleanup; } diff --git a/tests/network/remote/remotes.c b/tests/network/remote/remotes.c index 306ccaee5..333b52a5b 100644 --- a/tests/network/remote/remotes.c +++ b/tests/network/remote/remotes.c @@ -60,6 +60,15 @@ void test_network_remote_remotes__pushurl(void) cl_assert(git_remote_pushurl(_remote) == NULL); } +void test_network_remote_remotes__error_when_not_found(void) +{ + git_remote *r; + cl_git_fail_with(git_remote_load(&r, _repo, "does-not-exist"), GIT_ENOTFOUND); + + cl_assert(giterr_last() != NULL); + cl_assert(giterr_last()->klass == GITERR_CONFIG); +} + void test_network_remote_remotes__error_when_no_push_available(void) { git_remote *r; From 947a58c17557d634f16f7efc547b5b236575a3b9 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Fri, 30 May 2014 13:19:49 -0700 Subject: [PATCH 87/95] Clean up the handling of large binary diffs --- src/diff_print.c | 86 +++++++++++++++++++++++++++--------------------- src/util.h | 7 ++++ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index 26753515b..72fe69482 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -286,50 +286,46 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) { git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL; const void *old_data, *new_data; - git_off_t off_t_old_data_len, off_t_new_data_len; - unsigned long old_data_len, new_data_len, delta_data_len, inflated_len; - size_t remain; + git_off_t old_data_len, new_data_len; + unsigned long delta_data_len, inflated_len; const char *out_type = "literal"; - char *ptr; + char *scan, *end; int error; old_data = old ? git_blob_rawcontent(old) : NULL; new_data = new ? git_blob_rawcontent(new) : NULL; - off_t_old_data_len = old ? git_blob_rawsize(old) : 0; - off_t_new_data_len = new ? git_blob_rawsize(new) : 0; + old_data_len = old ? git_blob_rawsize(old) : 0; + new_data_len = new ? git_blob_rawsize(new) : 0; - /* The git_delta function accepts unsigned long only */ - if (off_t_old_data_len > ULONG_MAX || off_t_new_data_len > ULONG_MAX) { - error = -1; + if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len)) { + error = GIT_EBUFS; goto done; } - old_data_len = (unsigned long)off_t_old_data_len; - new_data_len = (unsigned long)off_t_new_data_len; - out = &deflate; - inflated_len = new_data_len; + inflated_len = (unsigned long)new_data_len; - if ((error = git_zstream_deflatebuf( - &deflate, new_data, new_data_len)) < 0) + if ((error = git_zstream_deflatebuf(out, new_data, (size_t)new_data_len)) < 0) goto done; /* The git_delta function accepts unsigned long only */ - if (deflate.size > ULONG_MAX) { - error = -1; + if (!git__is_ulong((git_off_t)deflate.size)) { + error = GIT_EBUFS; goto done; } if (old && new) { - void *delta_data; - - delta_data = git_delta(old_data, old_data_len, new_data, - new_data_len, &delta_data_len, (unsigned long)deflate.size); + void *delta_data = git_delta( + old_data, (unsigned long)old_data_len, + new_data, (unsigned long)new_data_len, + &delta_data_len, (unsigned long)deflate.size); if (delta_data) { - error = git_zstream_deflatebuf(&delta, delta_data, delta_data_len); - free(delta_data); + error = git_zstream_deflatebuf( + &delta, delta_data, (size_t)delta_data_len); + + git__free(delta_data); if (error < 0) goto done; @@ -345,15 +341,17 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) git_buf_printf(pi->buf, "%s %lu\n", out_type, inflated_len); pi->line.num_lines++; - for (ptr = out->ptr, remain = out->size; remain > 0; ) { - size_t chunk_len = (52 < remain) ? 52 : remain; + for (scan = out->ptr, end = out->ptr + out->size; scan < end; ) { + size_t chunk_len = end - scan; + if (chunk_len > 52) + chunk_len = 52; if (chunk_len <= 26) git_buf_putc(pi->buf, (char)chunk_len + 'A' - 1); else git_buf_putc(pi->buf, (char)chunk_len - 26 + 'a' - 1); - git_buf_put_base85(pi->buf, ptr, chunk_len); + git_buf_put_base85(pi->buf, scan, chunk_len); git_buf_putc(pi->buf, '\n'); if (git_buf_oom(pi->buf)) { @@ -361,8 +359,7 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) goto done; } - ptr += chunk_len; - remain -= chunk_len; + scan += chunk_len; pi->line.num_lines++; } @@ -381,26 +378,33 @@ static int diff_print_patch_file_binary( git_blob *old = NULL, *new = NULL; const git_oid *old_id, *new_id; int error; + size_t pre_binary_size; - if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) { - pi->line.num_lines = 1; - return diff_delta_format_with_paths( - pi->buf, delta, oldpfx, newpfx, - "Binary files %s%s and %s%s differ\n"); - } + if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) + goto noshow; + pre_binary_size = pi->buf->size; git_buf_printf(pi->buf, "GIT binary patch\n"); pi->line.num_lines++; old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL; new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL; - if ((old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) || - (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) || - (error = print_binary_hunk(pi, old, new)) < 0 || + if (old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) + goto done; + if (new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) + goto done; + + if ((error = print_binary_hunk(pi, old, new)) < 0 || (error = git_buf_putc(pi->buf, '\n')) < 0 || (error = print_binary_hunk(pi, new, old)) < 0) - goto done; + { + if (error == GIT_EBUFS) { + giterr_clear(); + git_buf_truncate(pi->buf, pre_binary_size); + goto noshow; + } + } pi->line.num_lines++; @@ -409,6 +413,12 @@ done: git_blob_free(new); return error; + +noshow: + pi->line.num_lines = 1; + return diff_delta_format_with_paths( + pi->buf, delta, oldpfx, newpfx, + "Binary files %s%s and %s%s differ\n"); } static int diff_print_patch_file( diff --git a/src/util.h b/src/util.h index 6fb2dc0f4..ca676c039 100644 --- a/src/util.h +++ b/src/util.h @@ -133,6 +133,13 @@ GIT_INLINE(int) git__is_uint32(size_t p) return p == (size_t)r; } +/** @return true if p fits into the range of an unsigned long */ +GIT_INLINE(int) git__is_ulong(git_off_t p) +{ + unsigned long r = (unsigned long)p; + return p == (git_off_t)r; +} + /* 32-bit cross-platform rotl */ #ifdef _MSC_VER /* use built-in method in MSVC */ # define git__rotl(v, s) (uint32_t)_rotl(v, s) From bc81220dfcf3419085c545b4a271aa04178c6960 Mon Sep 17 00:00:00 2001 From: Russell Belfer Date: Sat, 31 May 2014 10:19:55 -0700 Subject: [PATCH 88/95] minor cleanups --- src/diff_print.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/diff_print.c b/src/diff_print.c index 72fe69482..bb925ef98 100644 --- a/src/diff_print.c +++ b/src/diff_print.c @@ -298,15 +298,15 @@ static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new) old_data_len = old ? git_blob_rawsize(old) : 0; new_data_len = new ? git_blob_rawsize(new) : 0; - if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len)) { - error = GIT_EBUFS; - goto done; - } + /* The git_delta function accepts unsigned long only */ + if (!git__is_ulong(old_data_len) || !git__is_ulong(new_data_len)) + return GIT_EBUFS; out = &deflate; inflated_len = (unsigned long)new_data_len; - if ((error = git_zstream_deflatebuf(out, new_data, (size_t)new_data_len)) < 0) + if ((error = git_zstream_deflatebuf( + out, new_data, (size_t)new_data_len)) < 0) goto done; /* The git_delta function accepts unsigned long only */ From 8a9419aae185204c9f727285643ab99b1b968610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 1 Jun 2014 02:16:07 +0200 Subject: [PATCH 89/95] remote: build up the list of refs to remove When removing the remote-tracking branches, build up the list and remove in two steps, working around an issue with the iterator. Removing while we're iterating over the refs can cause us to miss references. --- src/remote.c | 42 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/remote.c b/src/remote.c index f2e2e2f7a..b56bf3b24 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1809,24 +1809,50 @@ static int remove_branch_config_related_entries( return error; } -static int remove_refs(git_repository *repo, const char *glob) +static int remove_refs(git_repository *repo, const git_refspec *spec) { - git_reference_iterator *iter; + git_reference_iterator *iter = NULL; + git_vector refs; const char *name; + char *dup; int error; + size_t i; - if ((error = git_reference_iterator_glob_new(&iter, repo, glob)) < 0) + if ((error = git_vector_init(&refs, 8, NULL)) < 0) return error; + if ((error = git_reference_iterator_new(&iter, repo)) < 0) + goto cleanup; + while ((error = git_reference_next_name(&name, iter)) == 0) { + if (!git_refspec_dst_matches(spec, name)) + continue; + + dup = git__strdup(name); + if (!dup) { + error = -1; + goto cleanup; + } + + if ((error = git_vector_insert(&refs, dup)) < 0) + goto cleanup; + } + if (error == GIT_ITEROVER) + error = 0; + if (error < 0) + goto cleanup; + + git_vector_foreach(&refs, i, name) { if ((error = git_reference_remove(repo, name)) < 0) break; } + +cleanup: git_reference_iterator_free(iter); - - if (error == GIT_ITEROVER) - error = 0; - + git_vector_foreach(&refs, i, dup) { + git__free(dup); + } + git_vector_free(&refs); return error; } @@ -1848,7 +1874,7 @@ static int remove_remote_tracking(git_repository *repo, const char *remote_name) if (refspec == NULL) continue; - if ((error = remove_refs(repo, git_refspec_dst(refspec))) < 0) + if ((error = remove_refs(repo, refspec)) < 0) break; } From 4ee2543c5ac1e7ed2f849968a7396f04d83fee54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 2 Jun 2014 16:46:47 +0200 Subject: [PATCH 90/95] refs: failing test for concurrent ref access If we remove a reference while we're iterating through the packed refs, the position in the iterator will be off. --- tests/refs/iterator.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/refs/iterator.c b/tests/refs/iterator.c index a29b0cf8b..c77451309 100644 --- a/tests/refs/iterator.c +++ b/tests/refs/iterator.c @@ -186,3 +186,36 @@ void test_refs_iterator__foreach_name_can_cancel(void) -333); cl_assert_equal_i(0, cancel_after); } + +void test_refs_iterator__concurrent_delete(void) +{ + git_reference_iterator *iter; + size_t full_count = 0, concurrent_count = 0; + const char *name; + int error; + + git_repository_free(repo); + repo = cl_git_sandbox_init("testrepo"); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + full_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_git_pass(git_reference_iterator_new(&iter, repo)); + while ((error = git_reference_next_name(&name, iter)) == 0) { + cl_git_pass(git_reference_remove(repo, name)); + concurrent_count++; + } + + git_reference_iterator_free(iter); + cl_assert_equal_i(GIT_ITEROVER, error); + + cl_assert_equal_i(full_count, concurrent_count); + + cl_git_sandbox_cleanup(); + repo = NULL; +} From 2d945f82f63a4df57b2bd9ba6551d99d4517f7f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 2 Jun 2014 17:44:51 +0200 Subject: [PATCH 91/95] refs: copy the packed refs on iteration This lets us work without worrying about what's happening but work on a snapshot. --- src/refdb_fs.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/refdb_fs.c b/src/refdb_fs.c index dd8bf7916..0e36ca8ac 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -458,6 +458,7 @@ typedef struct { git_pool pool; git_vector loose; + git_sortedcache *cache; size_t loose_pos; size_t packed_pos; } refdb_fs_iter; @@ -468,6 +469,7 @@ static void refdb_fs_backend__iterator_free(git_reference_iterator *_iter) git_vector_free(&iter->loose); git_pool_clear(&iter->pool); + git_sortedcache_free(iter->cache); git__free(iter); } @@ -539,10 +541,14 @@ static int refdb_fs_backend__iterator_next( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -556,7 +562,6 @@ static int refdb_fs_backend__iterator_next( break; } - git_sortedcache_runlock(backend->refcache); return error; } @@ -579,10 +584,14 @@ static int refdb_fs_backend__iterator_next_name( giterr_clear(); } - git_sortedcache_rlock(backend->refcache); + if (!iter->cache) { + if ((error = git_sortedcache_copy(&iter->cache, backend->refcache, 1, NULL, NULL)) < 0) + return error; + } - while (iter->packed_pos < git_sortedcache_entrycount(backend->refcache)) { - ref = git_sortedcache_entry(backend->refcache, iter->packed_pos++); + error = GIT_ITEROVER; + while (iter->packed_pos < git_sortedcache_entrycount(iter->cache)) { + ref = git_sortedcache_entry(iter->cache, iter->packed_pos++); if (!ref) /* stop now if another thread deleted refs and we past end */ break; @@ -596,7 +605,6 @@ static int refdb_fs_backend__iterator_next_name( break; } - git_sortedcache_runlock(backend->refcache); return error; } From 11e2665e5040066cc251a45d017f37d2b0bd0617 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 2 Jun 2014 18:53:32 +0200 Subject: [PATCH 92/95] Formatting fixes for the docs These are some issues I found while playing around with the new parser for docurium. --- include/git2/checkout.h | 24 ++++++++--------- include/git2/diff.h | 58 ++++++++++++++++++++--------------------- include/git2/index.h | 2 +- include/git2/reset.h | 6 ++--- include/git2/types.h | 26 +++++++++--------- 5 files changed, 58 insertions(+), 58 deletions(-) diff --git a/include/git2/checkout.h b/include/git2/checkout.h index 494f67456..ad44173e6 100644 --- a/include/git2/checkout.h +++ b/include/git2/checkout.h @@ -43,17 +43,17 @@ GIT_BEGIN_DECL * In between those are `GIT_CHECKOUT_SAFE` and `GIT_CHECKOUT_SAFE_CREATE` * both of which only make modifications that will not lose changes. * - * | target == baseline | target != baseline | - * ---------------------|-----------------------|----------------------| - * workdir == baseline | no action | create, update, or | - * | | delete file | - * ---------------------|-----------------------|----------------------| - * workdir exists and | no action | conflict (notify | - * is != baseline | notify dirty MODIFIED | and cancel checkout) | - * ---------------------|-----------------------|----------------------| - * workdir missing, | create if SAFE_CREATE | create file | - * baseline present | notify dirty DELETED | | - * ---------------------|-----------------------|----------------------| + * | target == baseline | target != baseline | + * ---------------------|-----------------------|----------------------| + * workdir == baseline | no action | create, update, or | + * | | delete file | + * ---------------------|-----------------------|----------------------| + * workdir exists and | no action | conflict (notify | + * is != baseline | notify dirty MODIFIED | and cancel checkout) | + * ---------------------|-----------------------|----------------------| + * workdir missing, | create if SAFE_CREATE | create file | + * baseline present | notify dirty DELETED | | + * ---------------------|-----------------------|----------------------| * * The only difference between SAFE and SAFE_CREATE is that SAFE_CREATE * will cause a file to be checked out if it is missing from the working @@ -106,7 +106,7 @@ GIT_BEGIN_DECL * target contains that file. */ typedef enum { - GIT_CHECKOUT_NONE = 0, /** default is a dry run, no actual updates */ + GIT_CHECKOUT_NONE = 0, /**< default is a dry run, no actual updates */ /** Allow safe updates that cannot overwrite uncommitted data */ GIT_CHECKOUT_SAFE = (1u << 0), diff --git a/include/git2/diff.h b/include/git2/diff.h index b40cc6135..675c209be 100644 --- a/include/git2/diff.h +++ b/include/git2/diff.h @@ -212,9 +212,9 @@ typedef struct git_diff git_diff; * considered reserved for internal or future use. */ typedef enum { - GIT_DIFF_FLAG_BINARY = (1u << 0), /** file(s) treated as binary data */ - GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /** file(s) treated as text data */ - GIT_DIFF_FLAG_VALID_ID = (1u << 2), /** `id` value is known correct */ + GIT_DIFF_FLAG_BINARY = (1u << 0), /**< file(s) treated as binary data */ + GIT_DIFF_FLAG_NOT_BINARY = (1u << 1), /**< file(s) treated as text data */ + GIT_DIFF_FLAG_VALID_ID = (1u << 2), /**< `id` value is known correct */ } git_diff_flag_t; /** @@ -228,15 +228,15 @@ typedef enum { * DELETED pairs). */ typedef enum { - GIT_DELTA_UNMODIFIED = 0, /** no changes */ - GIT_DELTA_ADDED = 1, /** entry does not exist in old version */ - GIT_DELTA_DELETED = 2, /** entry does not exist in new version */ - GIT_DELTA_MODIFIED = 3, /** entry content changed between old and new */ - GIT_DELTA_RENAMED = 4, /** entry was renamed between old and new */ - GIT_DELTA_COPIED = 5, /** entry was copied from another old entry */ - GIT_DELTA_IGNORED = 6, /** entry is ignored item in workdir */ - GIT_DELTA_UNTRACKED = 7, /** entry is untracked item in workdir */ - GIT_DELTA_TYPECHANGE = 8, /** type of entry changed between old and new */ + GIT_DELTA_UNMODIFIED = 0, /**< no changes */ + GIT_DELTA_ADDED = 1, /**< entry does not exist in old version */ + GIT_DELTA_DELETED = 2, /**< entry does not exist in new version */ + GIT_DELTA_MODIFIED = 3, /**< entry content changed between old and new */ + GIT_DELTA_RENAMED = 4, /**< entry was renamed between old and new */ + GIT_DELTA_COPIED = 5, /**< entry was copied from another old entry */ + GIT_DELTA_IGNORED = 6, /**< entry is ignored item in workdir */ + GIT_DELTA_UNTRACKED = 7, /**< entry is untracked item in workdir */ + GIT_DELTA_TYPECHANGE = 8, /**< type of entry changed between old and new */ } git_delta_t; /** @@ -416,12 +416,12 @@ typedef int (*git_diff_file_cb)( */ typedef struct git_diff_hunk git_diff_hunk; struct git_diff_hunk { - int old_start; /** Starting line number in old_file */ - int old_lines; /** Number of lines in old_file */ - int new_start; /** Starting line number in new_file */ - int new_lines; /** Number of lines in new_file */ - size_t header_len; /** Number of bytes in header text */ - char header[128]; /** Header text, NUL-byte terminated */ + int old_start; /**< Starting line number in old_file */ + int old_lines; /**< Number of lines in old_file */ + int new_start; /**< Starting line number in new_file */ + int new_lines; /**< Number of lines in new_file */ + size_t header_len; /**< Number of bytes in header text */ + char header[128]; /**< Header text, NUL-byte terminated */ }; /** @@ -464,13 +464,13 @@ typedef enum { */ typedef struct git_diff_line git_diff_line; struct git_diff_line { - char origin; /** A git_diff_line_t value */ - int old_lineno; /** Line number in old file or -1 for added line */ - int new_lineno; /** Line number in new file or -1 for deleted line */ - int num_lines; /** Number of newline characters in content */ - size_t content_len; /** Number of bytes of data */ - git_off_t content_offset; /** Offset in the original file to the content */ - const char *content; /** Pointer to diff text, not NUL-byte terminated */ + char origin; /**< A git_diff_line_t value */ + int old_lineno; /**< Line number in old file or -1 for added line */ + int new_lineno; /**< Line number in new file or -1 for deleted line */ + int num_lines; /**< Number of newline characters in content */ + size_t content_len; /**< Number of bytes of data */ + git_off_t content_offset; /**< Offset in the original file to the content */ + const char *content; /**< Pointer to diff text, not NUL-byte terminated */ }; /** @@ -482,10 +482,10 @@ struct git_diff_line { * of lines of file and hunk headers. */ typedef int (*git_diff_line_cb)( - const git_diff_delta *delta, /** delta that contains this data */ - const git_diff_hunk *hunk, /** hunk containing this data */ - const git_diff_line *line, /** line data */ - void *payload); /** user reference data */ + const git_diff_delta *delta, /**< delta that contains this data */ + const git_diff_hunk *hunk, /**< hunk containing this data */ + const git_diff_line *line, /**< line data */ + void *payload); /**< user reference data */ /** * Flags to control the behavior of diff rename/copy detection. diff --git a/include/git2/index.h b/include/git2/index.h index cdb87282c..b08329e2f 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -415,7 +415,7 @@ GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_en * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT * * @param entry The entry - * @returns the stage number + * @return the stage number */ GIT_EXTERN(int) git_index_entry_stage(const git_index_entry *entry); diff --git a/include/git2/reset.h b/include/git2/reset.h index 1759cc036..b8c580339 100644 --- a/include/git2/reset.h +++ b/include/git2/reset.h @@ -19,9 +19,9 @@ GIT_BEGIN_DECL * Kinds of reset operation */ typedef enum { - GIT_RESET_SOFT = 1, /** Move the head to the given commit */ - GIT_RESET_MIXED = 2, /** SOFT plus reset index to the commit */ - GIT_RESET_HARD = 3, /** MIXED plus changes in working tree discarded */ + GIT_RESET_SOFT = 1, /**< Move the head to the given commit */ + GIT_RESET_MIXED = 2, /**< SOFT plus reset index to the commit */ + GIT_RESET_HARD = 3, /**< MIXED plus changes in working tree discarded */ } git_reset_t; /** diff --git a/include/git2/types.h b/include/git2/types.h index 1b6f4cca1..6295ebbfa 100644 --- a/include/git2/types.h +++ b/include/git2/types.h @@ -154,15 +154,15 @@ typedef struct git_packbuilder git_packbuilder; /** Time in a signature */ typedef struct git_time { - git_time_t time; /** time in seconds from epoch */ - int offset; /** timezone offset, in minutes */ + git_time_t time; /**< time in seconds from epoch */ + int offset; /**< timezone offset, in minutes */ } git_time; /** An action signature (e.g. for committers, taggers, etc) */ typedef struct git_signature { - char *name; /** full name of the author */ - char *email; /** email of the author */ - git_time when; /** time when the action happened */ + char *name; /**< full name of the author */ + char *email; /**< email of the author */ + git_time when; /**< time when the action happened */ } git_signature; /** In-memory representation of a reference. */ @@ -183,9 +183,9 @@ typedef struct git_status_list git_status_list; /** Basic type of any Git reference. */ typedef enum { - GIT_REF_INVALID = 0, /** Invalid reference */ - GIT_REF_OID = 1, /** A reference which points at an object id */ - GIT_REF_SYMBOLIC = 2, /** A reference which points at another reference */ + GIT_REF_INVALID = 0, /**< Invalid reference */ + GIT_REF_OID = 1, /**< A reference which points at an object id */ + GIT_REF_SYMBOLIC = 2, /**< A reference which points at another reference */ GIT_REF_LISTALL = GIT_REF_OID|GIT_REF_SYMBOLIC, } git_ref_t; @@ -314,12 +314,12 @@ typedef enum { * when we don't want any particular ignore rule to be specified. */ typedef enum { - GIT_SUBMODULE_IGNORE_RESET = -1, /* reset to on-disk value */ + GIT_SUBMODULE_IGNORE_RESET = -1, /**< reset to on-disk value */ - GIT_SUBMODULE_IGNORE_NONE = 1, /* any change or untracked == dirty */ - GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /* dirty if tracked files change */ - GIT_SUBMODULE_IGNORE_DIRTY = 3, /* only dirty if HEAD moved */ - GIT_SUBMODULE_IGNORE_ALL = 4, /* never dirty */ + GIT_SUBMODULE_IGNORE_NONE = 1, /**< any change or untracked == dirty */ + GIT_SUBMODULE_IGNORE_UNTRACKED = 2, /**< dirty if tracked files change */ + GIT_SUBMODULE_IGNORE_DIRTY = 3, /**< only dirty if HEAD moved */ + GIT_SUBMODULE_IGNORE_ALL = 4, /**< never dirty */ GIT_SUBMODULE_IGNORE_DEFAULT = 0 } git_submodule_ignore_t; From dedfc7346b7873bfeb04ce06257bfa712d9632e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 2 Jun 2014 19:21:24 +0200 Subject: [PATCH 93/95] index: split GIT_IDXENTRY into two flag enums The documentation has shown this as a single enum for a long time. These should in fact be two enums. One with the bits for the flags and another with the bits for the extended flags. --- include/git2/index.h | 61 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/include/git2/index.h b/include/git2/index.h index b08329e2f..0b4476b4e 100644 --- a/include/git2/index.h +++ b/include/git2/index.h @@ -73,10 +73,13 @@ typedef struct git_index_entry { */ #define GIT_IDXENTRY_NAMEMASK (0x0fff) #define GIT_IDXENTRY_STAGEMASK (0x3000) -#define GIT_IDXENTRY_EXTENDED (0x4000) -#define GIT_IDXENTRY_VALID (0x8000) #define GIT_IDXENTRY_STAGESHIFT 12 +typedef enum { + GIT_IDXENTRY_EXTENDED = (0x4000), + GIT_IDXENTRY_VALID = (0x8000), +} git_indxentry_flag_t; + #define GIT_IDXENTRY_STAGE(E) \ (((E)->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT) @@ -92,36 +95,36 @@ typedef struct git_index_entry { * in-memory only and used by libgit2. Only the flags in * `GIT_IDXENTRY_EXTENDED_FLAGS` will get saved on-disk. * - * These bitmasks match the three fields in the `git_index_entry` - * `flags_extended` value that belong on disk. You can use them to - * interpret the data in the `flags_extended`. - */ -#define GIT_IDXENTRY_INTENT_TO_ADD (1 << 13) -#define GIT_IDXENTRY_SKIP_WORKTREE (1 << 14) -/* GIT_IDXENTRY_EXTENDED2 is reserved for future extension */ -#define GIT_IDXENTRY_EXTENDED2 (1 << 15) - -#define GIT_IDXENTRY_EXTENDED_FLAGS (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE) - -/** - * Bitmasks for in-memory only fields of `git_index_entry`'s `flags_extended` - * - * These bitmasks match the other fields in the `git_index_entry` - * `flags_extended` value that are only used in-memory by libgit2. You + * Thee first three bitmasks match the three fields in the + * `git_index_entry` `flags_extended` value that belong on disk. You * can use them to interpret the data in the `flags_extended`. + * + * The rest of the bitmasks match the other fields in the `git_index_entry` + * `flags_extended` value that are only used in-memory by libgit2. + * You can use them to interpret the data in the `flags_extended`. + * */ -#define GIT_IDXENTRY_UPDATE (1 << 0) -#define GIT_IDXENTRY_REMOVE (1 << 1) -#define GIT_IDXENTRY_UPTODATE (1 << 2) -#define GIT_IDXENTRY_ADDED (1 << 3) +typedef enum { -#define GIT_IDXENTRY_HASHED (1 << 4) -#define GIT_IDXENTRY_UNHASHED (1 << 5) -#define GIT_IDXENTRY_WT_REMOVE (1 << 6) /* remove in work directory */ -#define GIT_IDXENTRY_CONFLICTED (1 << 7) + GIT_IDXENTRY_INTENT_TO_ADD = (1 << 13), + GIT_IDXENTRY_SKIP_WORKTREE = (1 << 14), + /** Reserved for future extension */ + GIT_IDXENTRY_EXTENDED2 = (1 << 15), -#define GIT_IDXENTRY_UNPACKED (1 << 8) -#define GIT_IDXENTRY_NEW_SKIP_WORKTREE (1 << 9) + GIT_IDXENTRY_EXTENDED_FLAGS = (GIT_IDXENTRY_INTENT_TO_ADD | GIT_IDXENTRY_SKIP_WORKTREE), + GIT_IDXENTRY_UPDATE = (1 << 0), + GIT_IDXENTRY_REMOVE = (1 << 1), + GIT_IDXENTRY_UPTODATE = (1 << 2), + GIT_IDXENTRY_ADDED = (1 << 3), + + GIT_IDXENTRY_HASHED = (1 << 4), + GIT_IDXENTRY_UNHASHED = (1 << 5), + GIT_IDXENTRY_WT_REMOVE = (1 << 6), /**< remove in work directory */ + GIT_IDXENTRY_CONFLICTED = (1 << 7), + + GIT_IDXENTRY_UNPACKED = (1 << 8), + GIT_IDXENTRY_NEW_SKIP_WORKTREE = (1 << 9), +} git_idxentry_extended_flag_t; /** Capabilities of system that affect index actions. */ typedef enum { @@ -412,7 +415,7 @@ GIT_EXTERN(int) git_index_add(git_index *index, const git_index_entry *source_en * * This entry is calculated from the entry's flag attribute like this: * - * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT + * (entry->flags & GIT_IDXENTRY_STAGEMASK) >> GIT_IDXENTRY_STAGESHIFT * * @param entry The entry * @return the stage number From 69a1a6918c0fc76468329bbb3258d83db8f4c487 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 3 Jun 2014 16:18:08 +0200 Subject: [PATCH 94/95] Plug a leak in the tests --- tests/index/filemodes.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/index/filemodes.c b/tests/index/filemodes.c index e00b9c975..58d7935a0 100644 --- a/tests/index/filemodes.c +++ b/tests/index/filemodes.c @@ -166,4 +166,6 @@ void test_index_filemodes__invalid(void) entry.mode = GIT_FILEMODE_BLOB; cl_git_pass(git_index_add(index, &entry)); + + git_index_free(index); } From 18d7896cb00b9a4abe55cb461e12db4813e6a59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 3 Jun 2014 21:47:53 +0200 Subject: [PATCH 95/95] clone: re-use the local transport's path resolution Whe already worked out the kinks with the function used in the local transport. Expose it and make use of it in the local clone method instead of trying to work it out again. --- src/clone.c | 18 +++--------------- src/path.c | 18 ++++++++++++++++++ src/path.h | 3 +++ src/transports/local.c | 22 ++-------------------- 4 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/clone.c b/src/clone.c index 5aaa94ff6..6c4fb6727 100644 --- a/src/clone.c +++ b/src/clone.c @@ -472,11 +472,10 @@ static bool can_link(const char *src, const char *dst, int link) int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) { - int error, root, flags; + int error, flags; git_repository *src; git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; git_buf reflog_message = GIT_BUF_INIT; - const char *url; assert(repo && remote); @@ -490,19 +489,8 @@ int git_clone_local_into(git_repository *repo, git_remote *remote, const git_che * repo, if it's not rooted, the path should be relative to * the repository's worktree/gitdir. */ - url = git_remote_url(remote); - if (!git__prefixcmp(url, "file://")) - root = strlen("file://"); - else - root = git_path_root(url); - - if (root >= 0) - git_buf_puts(&src_path, url + root); - else - git_buf_joinpath(&src_path, repository_base(repo), url); - - if (git_buf_oom(&src_path)) - return -1; + if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; /* Copy .git/objects/ from the source to the target */ if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) { diff --git a/src/path.c b/src/path.c index e0b00a086..5beab97ed 100644 --- a/src/path.c +++ b/src/path.c @@ -1127,3 +1127,21 @@ int git_path_dirload_with_stat( return error; } + +int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) +{ + int error; + + /* If url_or_path begins with file:// treat it as a URL */ + if (!git__prefixcmp(url_or_path, "file://")) { + if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { + return error; + } + } else { /* We assume url_or_path is already a path */ + if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { + return error; + } + } + + return 0; +} diff --git a/src/path.h b/src/path.h index 3213c5104..3e6efe3de 100644 --- a/src/path.h +++ b/src/path.h @@ -438,4 +438,7 @@ extern int git_path_iconv(git_path_iconv_t *ic, char **in, size_t *inlen); extern bool git_path_does_fs_decompose_unicode(const char *root); +/* Used for paths to repositories on the filesystem */ +extern int git_path_from_url_or_path(git_buf *local_path_out, const char *url_or_path); + #endif diff --git a/src/transports/local.c b/src/transports/local.c index 038337d72..f859f0b70 100644 --- a/src/transports/local.c +++ b/src/transports/local.c @@ -175,24 +175,6 @@ on_error: return -1; } -static int path_from_url_or_path(git_buf *local_path_out, const char *url_or_path) -{ - int error; - - /* If url_or_path begins with file:// treat it as a URL */ - if (!git__prefixcmp(url_or_path, "file://")) { - if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) { - return error; - } - } else { /* We assume url_or_path is already a path */ - if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) { - return error; - } - } - - return 0; -} - /* * Try to open the url as a git directory. The direction doesn't * matter in this case because we're calculating the heads ourselves. @@ -222,7 +204,7 @@ static int local_connect( t->flags = flags; /* 'url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, url)) < 0) { git_buf_free(&buf); return error; } @@ -386,7 +368,7 @@ static int local_push( size_t j; /* 'push->remote->url' may be a url or path; convert to a path */ - if ((error = path_from_url_or_path(&buf, push->remote->url)) < 0) { + if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) { git_buf_free(&buf); return error; }