From 48ebea662a33a3b918143c014dde88e58e6d0a75 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Mon, 7 Jan 2013 00:20:13 +0100 Subject: 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); -- cgit v1.2.1 From 40e48ea40f5dfe0fbf786efc89d4cf297f4525e1 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 15 Nov 2013 15:36:37 +0000 Subject: 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); +} -- cgit v1.2.1 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: 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; - - 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; + size_t prefix_len; + const char *dot; - git_buf_clear(&config_entry); + prefix_len = strlen("remote."); + dot = strchr(name + prefix_len, '.'); - 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)); + assert(dot); -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; } -- cgit v1.2.1 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: 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); -- cgit v1.2.1