Commit 5f4739475307665f88ff1d52b35f2b17d3a9b869

Linquize 2014-09-22T23:17:35

remote: prune refs when fetching

diff --git a/include/git2/remote.h b/include/git2/remote.h
index 15a8d48..0d4ee99 100644
--- a/include/git2/remote.h
+++ b/include/git2/remote.h
@@ -375,6 +375,14 @@ GIT_EXTERN(int) git_remote_update_tips(
 		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
  *
  * Convenience function to connect to a remote, download the data,
@@ -585,6 +593,24 @@ GIT_EXTERN(void) git_remote_set_autotag(
 	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
  *
  * All remote-tracking branches and configuration settings
diff --git a/src/remote.c b/src/remote.c
index dd9b178..d5aac67 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 b79ace4..5b4d5ce 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;
 };