From 40e48ea40f5dfe0fbf786efc89d4cf297f4525e1 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 15 Nov 2013 15:36:37 +0000 Subject: [PATCH] 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); +}