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; };