summaryrefslogtreecommitdiff
path: root/src/remote.c
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2014-12-30 11:53:55 -0600
committerEdward Thomson <ethomson@edwardthomson.com>2014-12-30 11:53:55 -0600
commita3ef70bb405a05c16fb533d828da2510ff751224 (patch)
tree2f9d31f93a5f873620e37f59c737e56155d056d9 /src/remote.c
parentc4c47fc2861935d8e373319c0734ea7b2ce34a73 (diff)
parent8aba3d47cf96e24e02601175f900f0e851077c1a (diff)
downloadlibgit2-a3ef70bb405a05c16fb533d828da2510ff751224.tar.gz
Merge pull request #2761 from libgit2/cmn/fetch-prune
Remote-tracking branch prunning
Diffstat (limited to 'src/remote.c')
-rw-r--r--src/remote.c167
1 files changed, 167 insertions, 0 deletions
diff --git a/src/remote.c b/src/remote.c
index af6c3ff60..03b6f2b3e 100644
--- a/src/remote.c
+++ b/src/remote.c
@@ -288,6 +288,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 ||
@@ -443,6 +444,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;
@@ -913,6 +930,12 @@ int git_remote_fetch(
/* 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;
}
@@ -1067,6 +1090,145 @@ cleanup:
return error;
}
+/**
+ * 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;
+ 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_set_cmp(&remote_refs, find_head);
+
+ if ((error = prune_candidates(&candidates, remote)) < 0)
+ goto cleanup;
+
+ /*
+ * 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;
+
+ if ((error = git_refspec_rtransform(&buf, spec, refname)) < 0)
+ goto cleanup;
+
+ key.name = (char *) git_buf_cstr(&buf);
+ error = git_vector_search(&pos, &remote_refs, &key);
+ git_buf_free(&buf);
+
+ if (error < 0 && error != GIT_ENOTFOUND)
+ goto cleanup;
+
+ if (error == GIT_ENOTFOUND)
+ continue;
+
+ /* 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;
+
+ 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_vector_free(&remote_refs);
+ git_vector_free_deep(&candidates);
+ return error;
+}
+
static int update_tips_for_spec(
git_remote *remote,
git_refspec *spec,
@@ -1472,6 +1634,11 @@ 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;
+}
+
static int rename_remote_config_section(
git_repository *repo,
const char *old_name,