From 5f4739475307665f88ff1d52b35f2b17d3a9b869 Mon Sep 17 00:00:00 2001 From: Linquize Date: Mon, 22 Sep 2014 23:17:35 +0800 Subject: [PATCH 01/16] remote: prune refs when fetching --- include/git2/remote.h | 26 +++++++++++ src/remote.c | 103 ++++++++++++++++++++++++++++++++++++++++++ src/remote.h | 1 + 3 files changed, 130 insertions(+) diff --git a/include/git2/remote.h b/include/git2/remote.h index 15a8d481f..0d4ee99a0 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -374,6 +374,14 @@ GIT_EXTERN(int) git_remote_update_tips( const git_signature *signature, const char *reflog_message); +/** + * Prune tracking refs that are no longer present on remote + * + * @param remote the remote to prune + * @return 0 or an error code + */ +GIT_EXTERN(int) git_remote_prune(git_remote *remote); + /** * Download new data and update tips * @@ -584,6 +592,24 @@ GIT_EXTERN(void) git_remote_set_autotag( git_remote *remote, git_remote_autotag_option_t value); +/** + * Retrieve the ref-prune setting + * + * @param remote the remote to query + * @return the ref-prune setting + */ +GIT_EXTERN(int) git_remote_prune_refs(const git_remote *remote); + +/** + * Set the ref-prune setting + * + * @param remote the remote to configure + * @param value a boolean value + */ +GIT_EXTERN(void) git_remote_set_prune_refs( + git_remote *remote, + int value); + /** * Give the remote a new name * diff --git a/src/remote.c b/src/remote.c index dd9b17854..d5aac67de 100644 --- a/src/remote.c +++ b/src/remote.c @@ -287,6 +287,7 @@ int git_remote_dup(git_remote **dest, git_remote *source) remote->repo = source->repo; remote->download_tags = source->download_tags; remote->update_fetchhead = source->update_fetchhead; + remote->prune_refs = source->prune_refs; if (git_vector_init(&remote->refs, 32, NULL) < 0 || git_vector_init(&remote->refspecs, 2, NULL) < 0 || @@ -442,6 +443,22 @@ int git_remote_lookup(git_remote **out, git_repository *repo, const char *name) if (download_tags_value(remote, config) < 0) goto cleanup; + git_buf_clear(&buf); + git_buf_printf(&buf, "remote.%s.prune", name); + + if ((error = git_config_get_bool(&remote->prune_refs, config, git_buf_cstr(&buf))) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + + if ((error = git_config_get_bool(&remote->prune_refs, config, "fetch.prune")) < 0) { + if (error == GIT_ENOTFOUND) { + giterr_clear(); + error = 0; + } + } + } + } + /* Move the data over to where the matching functions can find them */ if (dwim_refspecs(&remote->active_refspecs, &remote->refspecs, &remote->refs) < 0) goto cleanup; @@ -887,6 +904,7 @@ int git_remote_fetch( { int error; git_buf reflog_msg_buf = GIT_BUF_INIT; + size_t i; /* Connect and download everything */ if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) != 0) @@ -909,6 +927,9 @@ int git_remote_fetch( remote->name ? remote->name : remote->url); } + if (remote->prune_refs && (error = git_remote_prune(remote)) < 0) + return error; + /* Create "remote/foo" branches for all remote branches */ error = git_remote_update_tips(remote, signature, git_buf_cstr(&reflog_msg_buf)); git_buf_free(&reflog_msg_buf); @@ -1066,6 +1087,78 @@ cleanup: return error; } +int git_remote_prune(git_remote *remote) +{ + git_strarray arr = { 0 }; + size_t i, j, k; + git_vector remote_refs = GIT_VECTOR_INIT; + git_refspec *spec; + int error; + + if ((error = git_reference_list(&arr, remote->repo)) < 0) + return error; + + if ((error = ls_to_vector(&remote_refs, remote)) < 0) + goto cleanup; + + git_vector_foreach(&remote->active_refspecs, k, spec) { + if (spec->push) + continue; + + for (i = 0; i < arr.count; ++i) { + char *prune_ref = arr.strings[i]; + int found = 0; + git_remote_head *remote_ref; + git_oid oid; + git_reference *ref; + + if (git_refspec_dst_matches(spec, prune_ref) != 1) + continue; + + git_vector_foreach(&remote_refs, j, remote_ref) { + git_buf buf = GIT_BUF_INIT; + + if (git_refspec_transform(&buf, spec, remote_ref->name) < 0) + continue; + + if (!git__strcmp(prune_ref, git_buf_cstr(&buf))) { + found = 1; + break; + } + } + + if (found) + continue; + + if ((error = git_reference_lookup(&ref, remote->repo, prune_ref)) >= 0) { + if (git_reference_type(ref) == GIT_REF_OID) { + git_oid_cpy(&oid, git_reference_target(ref)); + if ((error = git_reference_delete(ref)) < 0) { + git_reference_free(ref); + goto cleanup; + } + + if (remote->callbacks.update_tips != NULL) { + git_oid zero_oid; + + memset(&zero_oid, 0, sizeof(zero_oid)); + if (remote->callbacks.update_tips(prune_ref, &oid, &zero_oid, remote->callbacks.payload) < 0) { + git_reference_free(ref); + goto cleanup; + } + } + } + git_reference_free(ref); + } + } + } + +cleanup: + git_strarray_free(&arr); + git_vector_free(&remote_refs); + return error; +} + static int update_tips_for_spec( git_remote *remote, git_refspec *spec, @@ -1465,6 +1558,16 @@ void git_remote_set_autotag(git_remote *remote, git_remote_autotag_option_t valu remote->download_tags = value; } +int git_remote_prune_refs(const git_remote *remote) +{ + return remote->prune_refs; +} + +void git_remote_set_prune_refs(git_remote *remote, int value) +{ + remote->prune_refs = value; +} + static int rename_remote_config_section( git_repository *repo, const char *old_name, diff --git a/src/remote.h b/src/remote.h index b79ace438..5b4d5ce08 100644 --- a/src/remote.h +++ b/src/remote.h @@ -33,6 +33,7 @@ struct git_remote { unsigned int need_pack; git_remote_autotag_option_t download_tags; int update_fetchhead; + int prune_refs; int passed_refspecs; }; From b8fefcb9ca85d16046dbf12a1d62399d4f4eb6be Mon Sep 17 00:00:00 2001 From: Linquize Date: Tue, 23 Sep 2014 23:21:41 +0800 Subject: [PATCH 02/16] Add test for prune refs --- tests/network/fetchlocal.c | 128 +++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 736261b31..188b8c758 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -48,6 +48,134 @@ void test_network_fetchlocal__complete(void) git_repository_free(repo); } +void test_network_fetchlocal__prune(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(19, (int)refnames.count); + cl_assert(callcount > 0); + git_strarray_free(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_prune(origin)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(18, (int)refnames.count); + git_strarray_free(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_prune(origin)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(17, (int)refnames.count); + git_strarray_free(&refnames); + git_remote_free(origin); + + git_repository_free(remote_repo); + git_repository_free(repo); +} + +void test_network_fetchlocal__fetchprune(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_config *config; + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(19, (int)refnames.count); + cl_assert(callcount > 0); + git_strarray_free(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/br2")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_prune_refs(origin, 1); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(18, (int)refnames.count); + git_strarray_free(&refnames); + git_remote_free(origin); + + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/packed")); + cl_git_pass(git_reference_delete(ref)); + git_reference_free(ref); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); + git_config_free(config); + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(17, (int)refnames.count); + git_strarray_free(&refnames); + git_remote_free(origin); + + git_repository_free(remote_repo); + git_repository_free(repo); +} + static void cleanup_sandbox(void *unused) { GIT_UNUSED(unused); From ce4b57c6bc16545e4f824dc66087e2bc665af7ac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 27 Oct 2014 14:40:33 -0700 Subject: [PATCH 03/16] Fix calls to `git_remote_download` and `git_remote_fetch`. --- tests/network/fetchlocal.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 188b8c758..4b9aa1235 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -68,7 +68,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_download(origin, NULL)); cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); @@ -84,7 +84,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_download(origin, NULL)); cl_git_pass(git_remote_prune(origin)); cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); @@ -100,7 +100,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin)); + cl_git_pass(git_remote_download(origin, NULL)); cl_git_pass(git_remote_prune(origin)); cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); @@ -133,7 +133,7 @@ void test_network_fetchlocal__fetchprune(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(19, (int)refnames.count); @@ -148,7 +148,7 @@ void test_network_fetchlocal__fetchprune(void) cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_prune_refs(origin, 1); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(18, (int)refnames.count); @@ -165,7 +165,7 @@ void test_network_fetchlocal__fetchprune(void) cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); cl_assert_equal_i(1, git_remote_prune_refs(origin)); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_fetch(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(17, (int)refnames.count); From 439e19f632b766a26b507c68305019dc3cbde59c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 12 Nov 2014 17:12:30 -0800 Subject: [PATCH 04/16] Test that prune overlapping works as expected. --- src/remote.c | 1 - tests/network/fetchlocal.c | 64 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/remote.c b/src/remote.c index d5aac67de..8ffb89404 100644 --- a/src/remote.c +++ b/src/remote.c @@ -904,7 +904,6 @@ int git_remote_fetch( { int error; git_buf reflog_msg_buf = GIT_BUF_INIT; - size_t i; /* Connect and download everything */ if ((error = git_remote_connect(remote, GIT_DIRECTION_FETCH)) != 0) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 4b9aa1235..0e0d07b20 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -113,6 +113,70 @@ void test_network_fetchlocal__prune(void) git_repository_free(repo); } +void test_network_fetchlocal__prune_overlapping(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_strarray refnames = {0}; + git_reference *ref; + git_object *obj; + git_config *config; + + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + + cl_git_pass(git_revparse_single(&obj, remote_repo, "master")); + cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", git_object_id(obj), 1, NULL, NULL)); + git_object_free(obj); + + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin, NULL)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + git_config_free(config); + + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin, NULL)); + cl_git_pass(git_remote_prune(origin)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); + cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(20, (int)refnames.count); + + cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + + cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); + cl_git_pass(git_remote_download(origin, NULL)); + cl_git_pass(git_remote_prune(origin)); + cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); + cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); + cl_git_pass(git_reference_list(&refnames, repo)); + cl_assert_equal_i(20, (int)refnames.count); +} + void test_network_fetchlocal__fetchprune(void) { git_repository *repo; From 82eeba814285597c3259fb0cad39cade499d69f2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 12 Nov 2014 20:44:28 -0800 Subject: [PATCH 05/16] Fix references to git_remote_lookup. --- tests/network/fetchlocal.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 0e0d07b20..6f26ddad1 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -81,7 +81,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_reference_delete(ref)); git_reference_free(ref); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(origin, NULL)); @@ -97,7 +97,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_reference_delete(ref)); git_reference_free(ref); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(origin, NULL)); @@ -148,7 +148,7 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); git_config_free(config); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(origin, NULL)); @@ -164,7 +164,7 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); cl_git_pass(git_remote_download(origin, NULL)); @@ -209,7 +209,7 @@ void test_network_fetchlocal__fetchprune(void) cl_git_pass(git_reference_delete(ref)); git_reference_free(ref); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_prune_refs(origin, 1); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); @@ -226,7 +226,7 @@ void test_network_fetchlocal__fetchprune(void) cl_git_pass(git_repository_config(&config, repo)); cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); git_config_free(config); - cl_git_pass(git_remote_load(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); cl_assert_equal_i(1, git_remote_prune_refs(origin)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); From 93d968fa0736a9c6f45f9e7da8ef7af8991265e7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 12 Nov 2014 22:05:09 -0800 Subject: [PATCH 06/16] Cleanup after testing remote prune. --- tests/network/fetchlocal.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 6f26ddad1..67e1fac95 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -124,14 +124,13 @@ void test_network_fetchlocal__prune_overlapping(void) git_config *config; git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; cl_git_pass(git_revparse_single(&obj, remote_repo, "master")); cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", git_object_id(obj), 1, NULL, NULL)); git_object_free(obj); - const char *url = cl_git_path_url(git_repository_path(remote_repo)); - git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; - callbacks.transfer_progress = transfer_cb; callbacks.payload = &callcount; @@ -175,6 +174,10 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(20, (int)refnames.count); + + cl_git_pass(git_reference_delete(ref)); + git_repository_free(remote_repo); + git_repository_free(repo); } void test_network_fetchlocal__fetchprune(void) From b91194e8426899980b9af5be42c48ba0d89eff96 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 13 Nov 2014 09:22:10 -0800 Subject: [PATCH 07/16] Cleanup repository after prune tests. --- tests/network/fetchlocal.c | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 67e1fac95..1996a7078 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -67,9 +67,7 @@ void test_network_fetchlocal__prune(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, NULL)); - cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(19, (int)refnames.count); @@ -125,34 +123,27 @@ void test_network_fetchlocal__prune_overlapping(void) git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; cl_git_pass(git_revparse_single(&obj, remote_repo, "master")); cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", git_object_id(obj), 1, NULL, NULL)); - git_object_free(obj); - - callbacks.transfer_progress = transfer_cb; - callbacks.payload = &callcount; cl_set_cleanup(&cleanup_local_repo, "foo"); cl_git_pass(git_repository_init(&repo, "foo", true)); cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, NULL)); - cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); cl_git_pass(git_repository_config(&config, repo)); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); - git_config_free(config); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_prune_refs(origin, 1); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, NULL)); - cl_git_pass(git_remote_prune(origin)); - cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); @@ -165,10 +156,7 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); - cl_git_pass(git_remote_connect(origin, GIT_DIRECTION_FETCH)); - cl_git_pass(git_remote_download(origin, NULL)); - cl_git_pass(git_remote_prune(origin)); - cl_git_pass(git_remote_update_tips(origin, NULL, NULL)); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); @@ -176,6 +164,13 @@ void test_network_fetchlocal__prune_overlapping(void) cl_assert_equal_i(20, (int)refnames.count); cl_git_pass(git_reference_delete(ref)); + cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + + git_object_free(obj); + git_config_free(config); + git_strarray_free(&refnames); + git_remote_free(origin); git_repository_free(remote_repo); git_repository_free(repo); } From 5e0c3d2d1a57f0a3764bdb1dc0ddb07e96f4b127 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 13 Nov 2014 11:26:02 -0800 Subject: [PATCH 08/16] Make sure that `fetch --prune --tags` doesn't remove tags. --- tests/network/fetchlocal.c | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 1996a7078..49a14db29 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -4,6 +4,10 @@ #include "path.h" #include "remote.h" +static const char* tagger_name = "Vicent Marti"; +static const char* tagger_email = "vicent@github.com"; +static const char* tagger_message = "This is my tag.\n\nThere are many tags, but this one is mine\n"; + static int transfer_cb(const git_transfer_progress *stats, void *payload) { int *callcount = (int*)payload; @@ -238,6 +242,61 @@ void test_network_fetchlocal__fetchprune(void) git_repository_free(repo); } +void test_network_fetchlocal__prune_tag(void) +{ + git_repository *repo; + git_remote *origin; + int callcount = 0; + git_reference *ref; + git_config *config; + git_oid tag_id; + git_signature *tagger; + git_object *obj; + + git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); + const char *url = cl_git_path_url(git_repository_path(remote_repo)); + git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT; + + callbacks.transfer_progress = transfer_cb; + callbacks.payload = &callcount; + + cl_set_cleanup(&cleanup_local_repo, "foo"); + cl_git_pass(git_repository_init(&repo, "foo", true)); + + cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); + + cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/origin/fake-remote", git_object_id(obj), 1, NULL, NULL)); + + /* create signature */ + cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); + + cl_git_pass( + git_tag_create(&tag_id, repo, + "some-tag", obj, tagger, tagger_message, 0) + ); + + cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); + git_config_free(config); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + cl_assert_equal_i(1, git_remote_prune_refs(origin)); + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + + cl_git_pass(git_revparse_single(&obj, repo, "some-tag")); + cl_git_fail(git_revparse_single(&obj, repo, "refs/remotes/origin/fake-remote")); + + git_object_free(obj); + git_remote_free(origin); + + git_repository_free(remote_repo); + git_repository_free(repo); +} + static void cleanup_sandbox(void *unused) { GIT_UNUSED(unused); From 020aab9308fe30a9e7c9930514e4a5203981427e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 16:50:33 +0100 Subject: [PATCH 09/16] fetch: do set prune when testing We load the remote again, so we need to ask the new remote to prune the refs, or we're not exercising the code in our tests. --- tests/network/fetchlocal.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 49a14db29..9bfa48844 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -159,6 +159,7 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_prune_refs(origin, true); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); From 4aa23369660726479f2c3e4cbee2cd51497ad00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 16:56:38 +0100 Subject: [PATCH 10/16] fetch: assert we don't call update tips when there are no upates This is hiding a bug in the prune code, whereby we prune references we shouldn't but don't notice it in the code afterwards because update_tips() recreates them. This means that we do perform changes to the references (and get rid of the reflogs) when we shouldn't. --- tests/network/fetchlocal.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 9bfa48844..a278da3fb 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -115,6 +115,17 @@ void test_network_fetchlocal__prune(void) git_repository_free(repo); } +int update_tips_fail_on_call(const char *ref, const git_oid *old, const git_oid *new, void *data) +{ + GIT_UNUSED(ref); + GIT_UNUSED(old); + GIT_UNUSED(new); + GIT_UNUSED(data); + + cl_fail("update tips called"); + return 0; +} + void test_network_fetchlocal__prune_overlapping(void) { git_repository *repo; @@ -160,6 +171,7 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_prune_refs(origin, true); + callbacks.update_tips = update_tips_fail_on_call; git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); From 8c13eaedbb85a9f9211559a657c955d93f4af5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 17:00:54 +0100 Subject: [PATCH 11/16] fetch: prune after updating tips This makes a fetch+prune more similar to a connect+prune and makes it more likely that we see errors in the decision to prune a reference. --- src/remote.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/remote.c b/src/remote.c index 8ffb89404..cd814091d 100644 --- a/src/remote.c +++ b/src/remote.c @@ -926,12 +926,15 @@ int git_remote_fetch( remote->name ? remote->name : remote->url); } - if (remote->prune_refs && (error = git_remote_prune(remote)) < 0) - return error; - /* Create "remote/foo" branches for all remote branches */ error = git_remote_update_tips(remote, signature, git_buf_cstr(&reflog_msg_buf)); git_buf_free(&reflog_msg_buf); + if (error < 0) + return error; + + if (remote->prune_refs) + error = git_remote_prune(remote); + return error; } From 59ff8b679007fb7c50c1db713b666ad7d6cadca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 18:24:54 +0100 Subject: [PATCH 12/16] fetch: perform prune in separate steps For each remote-tracking branch we want to remove, we need to consider it against every other refspec in case we have overlapping refspecs, such as with refs/heads/*:refs/remotes/origin/* refs/pull/*/head:refs/remotes/origin/pr/* as we'd otherwise remove too many refspecs. Create a list of condidates, which are the references matching the rhs of any active refspec and then filter that list by removing those entries for which we find a remove reference with any active refspec. Those which are left after this are removed. --- src/remote.c | 155 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 111 insertions(+), 44 deletions(-) diff --git a/src/remote.c b/src/remote.c index cd814091d..28d7b55c2 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1089,75 +1089,142 @@ cleanup: return error; } -int git_remote_prune(git_remote *remote) +/** + * Generate a list of candidates for pruning by getting a list of + * references which match the rhs of an active refspec. + */ +static int prune_candidates(git_vector *candidates, git_remote *remote) { git_strarray arr = { 0 }; - size_t i, j, k; - git_vector remote_refs = GIT_VECTOR_INIT; - git_refspec *spec; + size_t i; int error; if ((error = git_reference_list(&arr, remote->repo)) < 0) return error; + for (i = 0; i < arr.count; i++) { + const char *refname = arr.strings[i]; + char *refname_dup; + + if (!git_remote__matching_dst_refspec(remote, refname)) + continue; + + refname_dup = git__strdup(refname); + GITERR_CHECK_ALLOC(refname_dup); + + if ((error = git_vector_insert(candidates, refname_dup)) < 0) + goto out; + } + +out: + git_strarray_free(&arr); + return error; +} + +static int find_head(const void *_a, const void *_b) +{ + git_remote_head *a = (git_remote_head *) _a; + git_remote_head *b = (git_remote_head *) _b; + + return strcmp(a->name, b->name); +} + +int git_remote_prune(git_remote *remote) +{ + size_t i, j; + git_vector remote_refs = GIT_VECTOR_INIT; + git_vector candidates = GIT_VECTOR_INIT; + const git_refspec *spec; + const char *refname; + int error; + git_oid zero_id = {{ 0 }}; + if ((error = ls_to_vector(&remote_refs, remote)) < 0) goto cleanup; - git_vector_foreach(&remote->active_refspecs, k, spec) { - if (spec->push) - continue; + git_vector_set_cmp(&remote_refs, find_head); - for (i = 0; i < arr.count; ++i) { - char *prune_ref = arr.strings[i]; - int found = 0; - git_remote_head *remote_ref; - git_oid oid; - git_reference *ref; + if ((error = prune_candidates(&candidates, remote)) < 0) + goto cleanup; - if (git_refspec_dst_matches(spec, prune_ref) != 1) + /* + * Remove those entries from the candidate list for which we + * can find a remote reference in at least one refspec. + */ + git_vector_foreach(&candidates, i, refname) { + git_vector_foreach(&remote->active_refspecs, j, spec) { + git_buf buf = GIT_BUF_INIT; + size_t pos; + char *src_name; + git_remote_head key = {0}; + + if (!git_refspec_dst_matches(spec, refname)) continue; - git_vector_foreach(&remote_refs, j, remote_ref) { - git_buf buf = GIT_BUF_INIT; + if ((error = git_refspec_rtransform(&buf, spec, refname)) < 0) + goto cleanup; - if (git_refspec_transform(&buf, spec, remote_ref->name) < 0) - continue; + key.name = (char *) git_buf_cstr(&buf); + error = git_vector_search(&pos, &remote_refs, &key); + git_buf_free(&buf); - if (!git__strcmp(prune_ref, git_buf_cstr(&buf))) { - found = 1; - break; - } - } + if (error < 0 && error != GIT_ENOTFOUND) + goto cleanup; - if (found) + if (error == GIT_ENOTFOUND) continue; - if ((error = git_reference_lookup(&ref, remote->repo, prune_ref)) >= 0) { - if (git_reference_type(ref) == GIT_REF_OID) { - git_oid_cpy(&oid, git_reference_target(ref)); - if ((error = git_reference_delete(ref)) < 0) { - git_reference_free(ref); - goto cleanup; - } - - if (remote->callbacks.update_tips != NULL) { - git_oid zero_oid; + /* if we did find a source, remove it from the candiates */ + if ((error = git_vector_set((void **) &src_name, &candidates, i, NULL)) < 0) + goto cleanup; - memset(&zero_oid, 0, sizeof(zero_oid)); - if (remote->callbacks.update_tips(prune_ref, &oid, &zero_oid, remote->callbacks.payload) < 0) { - git_reference_free(ref); - goto cleanup; - } - } - } - git_reference_free(ref); - } + git__free(src_name); + break; } } + /* + * For those candidates still left in the list, we need to + * remove them. We do not remove symrefs, as those are for + * stuff like origin/HEAD which will never match, but we do + * not want to remove them. + */ + git_vector_foreach(&candidates, i, refname) { + git_reference *ref; + git_oid id; + + if (refname == NULL) + continue; + + error = git_reference_lookup(&ref, remote->repo, refname); + /* as we want it gone, let's not consider this an error */ + if (error == GIT_ENOTFOUND) + continue; + + if (error < 0) + goto cleanup; + + if (git_reference_type(ref) == GIT_REF_SYMBOLIC) { + git_reference_free(ref); + continue; + } + + git_oid_cpy(&id, git_reference_target(ref)); + error = git_reference_delete(ref); + git_reference_free(ref); + if (error < 0) + goto cleanup; + + if (remote->callbacks.update_tips) + error = remote->callbacks.update_tips(refname, &id, &zero_id, remote->callbacks.payload); + + if (error < 0) + goto cleanup; + } + cleanup: - git_strarray_free(&arr); git_vector_free(&remote_refs); + git_vector_free_deep(&candidates); return error; } From 7b6e1e4cc668e0c9774d8149bdddd10ac7deeed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 20:40:52 +0100 Subject: [PATCH 13/16] fetch: add test for the other order of overlapping specs --- tests/network/fetchlocal.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index a278da3fb..e2e6b5e52 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -180,6 +180,15 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(20, (int)refnames.count); + cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); + git_remote_set_prune_refs(origin, true); + callbacks.update_tips = update_tips_fail_on_call; + git_remote_set_callbacks(origin, &callbacks); + cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + cl_git_pass(git_reference_delete(ref)); cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); From 26186b155baf614bcea789d06e17fe7eb13715fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 21:01:19 +0100 Subject: [PATCH 14/16] fetch: remove the prune setter This option does not get persisted to disk, which makes it different from the rest of the setters. Remove it until we go all the way. We still respect the configuration option, and it's still possible to perform a one-time prune by calling the function. --- include/git2/remote.h | 10 ---------- src/remote.c | 5 ----- tests/network/fetchlocal.c | 6 ++---- 3 files changed, 2 insertions(+), 19 deletions(-) diff --git a/include/git2/remote.h b/include/git2/remote.h index 0d4ee99a0..9f37dc2f3 100644 --- a/include/git2/remote.h +++ b/include/git2/remote.h @@ -600,16 +600,6 @@ GIT_EXTERN(void) git_remote_set_autotag( */ GIT_EXTERN(int) git_remote_prune_refs(const git_remote *remote); -/** - * Set the ref-prune setting - * - * @param remote the remote to configure - * @param value a boolean value - */ -GIT_EXTERN(void) git_remote_set_prune_refs( - git_remote *remote, - int value); - /** * Give the remote a new name * diff --git a/src/remote.c b/src/remote.c index 28d7b55c2..f96aaa3c7 100644 --- a/src/remote.c +++ b/src/remote.c @@ -1632,11 +1632,6 @@ int git_remote_prune_refs(const git_remote *remote) return remote->prune_refs; } -void git_remote_set_prune_refs(git_remote *remote, int value) -{ - remote->prune_refs = value; -} - static int rename_remote_config_section( git_repository *repo, const char *old_name, diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index e2e6b5e52..88253391f 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -153,10 +153,10 @@ void test_network_fetchlocal__prune_overlapping(void) git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_repository_config(&config, repo)); + cl_git_pass(git_config_set_bool(config, "remote.origin.prune", true)); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - git_remote_set_prune_refs(origin, 1); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); @@ -170,7 +170,6 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - git_remote_set_prune_refs(origin, true); callbacks.update_tips = update_tips_fail_on_call; git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); @@ -184,7 +183,6 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - git_remote_set_prune_refs(origin, true); callbacks.update_tips = update_tips_fail_on_call; git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); @@ -234,9 +232,9 @@ void test_network_fetchlocal__fetchprune(void) git_reference_free(ref); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); - git_remote_set_prune_refs(origin, 1); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + cl_git_pass(git_remote_prune(origin)); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(18, (int)refnames.count); From 4adc64a81ae4d1ddb32c3232f04255f5b96d1971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Sun, 14 Dec 2014 21:24:46 +0100 Subject: [PATCH 15/16] fetch: plug leaks in the prune tests --- tests/network/fetchlocal.c | 52 +++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/tests/network/fetchlocal.c b/tests/network/fetchlocal.c index 88253391f..02d12e054 100644 --- a/tests/network/fetchlocal.c +++ b/tests/network/fetchlocal.c @@ -21,6 +21,11 @@ static void cleanup_local_repo(void *path) cl_fixture_cleanup((char *)path); } +void test_network_fetchlocal__cleanup(void) +{ + cl_git_sandbox_cleanup(); +} + void test_network_fetchlocal__complete(void) { git_repository *repo; @@ -111,7 +116,6 @@ void test_network_fetchlocal__prune(void) git_strarray_free(&refnames); git_remote_free(origin); - git_repository_free(remote_repo); git_repository_free(repo); } @@ -126,6 +130,14 @@ int update_tips_fail_on_call(const char *ref, const git_oid *old, const git_oid return 0; } +void assert_ref_exists(git_repository *repo, const char *name) +{ + git_reference *ref; + + cl_git_pass(git_reference_lookup(&ref, repo, name)); + git_reference_free(ref); +} + void test_network_fetchlocal__prune_overlapping(void) { git_repository *repo; @@ -133,8 +145,8 @@ void test_network_fetchlocal__prune_overlapping(void) int callcount = 0; git_strarray refnames = {0}; git_reference *ref; - git_object *obj; git_config *config; + git_oid target; git_repository *remote_repo = cl_git_sandbox_init("testrepo.git"); const char *url = cl_git_path_url(git_repository_path(remote_repo)); @@ -143,8 +155,11 @@ void test_network_fetchlocal__prune_overlapping(void) callbacks.transfer_progress = transfer_cb; callbacks.payload = &callcount; - cl_git_pass(git_revparse_single(&obj, remote_repo, "master")); - cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", git_object_id(obj), 1, NULL, NULL)); + cl_git_pass(git_reference_lookup(&ref, remote_repo, "refs/heads/master")); + git_oid_cpy(&target, git_reference_target(ref)); + git_reference_free(ref); + cl_git_pass(git_reference_create(&ref, remote_repo, "refs/pull/42/head", &target, 1, NULL, NULL)); + git_reference_free(ref); cl_set_cleanup(&cleanup_local_repo, "foo"); cl_git_pass(git_repository_init(&repo, "foo", true)); @@ -156,46 +171,46 @@ void test_network_fetchlocal__prune_overlapping(void) cl_git_pass(git_config_set_bool(config, "remote.origin.prune", true)); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + git_remote_free(origin); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); - cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); - cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); + assert_ref_exists(repo, "refs/remotes/origin/master"); + assert_ref_exists(repo, "refs/remotes/origin/pr/42"); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(20, (int)refnames.count); + git_strarray_free(&refnames); cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); + git_remote_free(origin); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); callbacks.update_tips = update_tips_fail_on_call; git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); - cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); - cl_git_pass(git_revparse_single(&obj, repo, "origin/pr/42")); + assert_ref_exists(repo, "refs/remotes/origin/master"); + assert_ref_exists(repo, "refs/remotes/origin/pr/42"); cl_git_pass(git_reference_list(&refnames, repo)); cl_assert_equal_i(20, (int)refnames.count); + git_strarray_free(&refnames); cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/pull/*/head:refs/remotes/origin/pr/*")); + + git_remote_free(origin); cl_git_pass(git_remote_lookup(&origin, repo, GIT_REMOTE_ORIGIN)); callbacks.update_tips = update_tips_fail_on_call; git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); - cl_git_pass(git_reference_delete(ref)); - cl_git_pass(git_config_delete_multivar(config, "remote.origin.fetch", "refs")); - cl_git_pass(git_config_set_multivar(config, "remote.origin.fetch", "^$", "refs/heads/*:refs/remotes/origin/*")); - - git_object_free(obj); git_config_free(config); git_strarray_free(&refnames); git_remote_free(origin); - git_repository_free(remote_repo); git_repository_free(repo); } @@ -258,7 +273,6 @@ void test_network_fetchlocal__fetchprune(void) git_strarray_free(&refnames); git_remote_free(origin); - git_repository_free(remote_repo); git_repository_free(repo); } @@ -286,10 +300,12 @@ void test_network_fetchlocal__prune_tag(void) cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url)); git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); + git_remote_free(origin); cl_git_pass(git_revparse_single(&obj, repo, "origin/master")); cl_git_pass(git_reference_create(&ref, repo, "refs/remotes/origin/fake-remote", git_object_id(obj), 1, NULL, NULL)); + git_reference_free(ref); /* create signature */ cl_git_pass(git_signature_new(&tagger, tagger_name, tagger_email, 123456789, 60)); @@ -298,6 +314,7 @@ void test_network_fetchlocal__prune_tag(void) git_tag_create(&tag_id, repo, "some-tag", obj, tagger, tagger_message, 0) ); + git_signature_free(tagger); cl_git_pass(git_repository_config(&config, repo)); cl_git_pass(git_config_set_bool(config, "remote.origin.prune", 1)); @@ -307,13 +324,12 @@ void test_network_fetchlocal__prune_tag(void) git_remote_set_callbacks(origin, &callbacks); cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL)); - cl_git_pass(git_revparse_single(&obj, repo, "some-tag")); - cl_git_fail(git_revparse_single(&obj, repo, "refs/remotes/origin/fake-remote")); + assert_ref_exists(repo, "refs/tags/some-tag"); + cl_git_fail_with(GIT_ENOTFOUND, git_reference_lookup(&ref, repo, "refs/remotes/origin/fake-remote")); git_object_free(obj); git_remote_free(origin); - git_repository_free(remote_repo); git_repository_free(repo); } From 8aba3d47cf96e24e02601175f900f0e851077c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 30 Dec 2014 17:06:04 +0000 Subject: [PATCH 16/16] Update CHANGELOG for pruning --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfb4f0430..4908a45f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -129,6 +129,11 @@ v0.21 + 1 has been changed to match git 1.9.0 and later. In this mode, libgit2 now fetches all tags in addition to whatever else needs to be fetched. +* The remote object has learnt to prune remote-tracking branches. If + the remote is configured to do so, this will happen via + git_remote_fetch(). You can also call git_remote_prune() after + connecting or fetching to perform the prune. + * git_threads_init() and git_threads_shutdown() have been renamed to git_libgit2_init() and git_libgit2_shutdown() to better explain what their purpose is, as it's grown to be more than just about threads.