diff --git a/builtin-push.c b/builtin-push.c
index 5f4d7d34d3..c45649e26c 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -54,38 +54,84 @@ static void expand_refspecs(void)
 	for_each_ref(expand_one_ref, NULL);
 }
 
+struct wildcard_cb {
+	const char *from_prefix;
+	int from_prefix_len;
+	const char *to_prefix;
+	int to_prefix_len;
+	int force;
+};
+
+static int expand_wildcard_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
+{
+	struct wildcard_cb *cb = cb_data;
+	int len = strlen(ref);
+	char *expanded, *newref;
+
+	if (len < cb->from_prefix_len ||
+	    memcmp(cb->from_prefix, ref, cb->from_prefix_len))
+		return 0;
+	expanded = xmalloc(len * 2 + cb->force +
+			   (cb->to_prefix_len - cb->from_prefix_len) + 2);
+	newref = expanded + cb->force;
+	if (cb->force)
+		expanded[0] = '+';
+	memcpy(newref, ref, len);
+	newref[len] = ':';
+	memcpy(newref + len + 1, cb->to_prefix, cb->to_prefix_len);
+	strcpy(newref + len + 1 + cb->to_prefix_len,
+	       ref + cb->from_prefix_len);
+	add_refspec(expanded);
+	return 0;
+}
+
+static int wildcard_ref(const char *ref)
+{
+	int len;
+	const char *colon;
+	struct wildcard_cb cb;
+
+	memset(&cb, 0, sizeof(cb));
+	if (ref[0] == '+') {
+		cb.force = 1;
+		ref++;
+	}
+	len = strlen(ref);
+	colon = strchr(ref, ':');
+	if (! (colon && ref < colon &&
+	       colon[-2] == '/' && colon[-1] == '*' &&
+	       /* "<mine>/<asterisk>:<yours>/<asterisk>" is at least 7 bytes */
+	       7 <= len &&
+	       ref[len-2] == '/' && ref[len-1] == '*') )
+		return 0 ;
+	cb.from_prefix = ref;
+	cb.from_prefix_len = colon - ref - 1;
+	cb.to_prefix = colon + 1;
+	cb.to_prefix_len = len - (colon - ref) - 2;
+	for_each_ref(expand_wildcard_ref, &cb);
+	return 1;
+}
+
 static void set_refspecs(const char **refs, int nr)
 {
 	if (nr) {
-		int pass;
-		for (pass = 0; pass < 2; pass++) {
-			/* pass 0 counts and allocates, pass 1 fills */
-			int i, cnt;
-			for (i = cnt = 0; i < nr; i++) {
-				if (!strcmp("tag", refs[i])) {
-					int len;
-					char *tag;
-					if (nr <= ++i)
-						die("tag <tag> shorthand without <tag>");
-					if (pass) {
-						len = strlen(refs[i]) + 11;
-						tag = xmalloc(len);
-						strcpy(tag, "refs/tags/");
-						strcat(tag, refs[i]);
-						refspec[cnt] = tag;
-					}
-					cnt++;
-					continue;
-				}
-				if (pass)
-					refspec[cnt] = refs[i];
-				cnt++;
-			}
-			if (!pass) {
-				size_t bytes = cnt * sizeof(char *);
-				refspec_nr = cnt;
-				refspec = xrealloc(refspec, bytes);
+		int i;
+		for (i = 0; i < nr; i++) {
+			const char *ref = refs[i];
+			if (!strcmp("tag", ref)) {
+				char *tag;
+				int len;
+				if (nr <= ++i)
+					die("tag shorthand without <tag>");
+				len = strlen(refs[i]) + 11;
+				tag = xmalloc(len);
+				strcpy(tag, "refs/tags/");
+				strcat(tag, refs[i]);
+				ref = tag;
 			}
+			else if (wildcard_ref(ref))
+				continue;
+			add_refspec(ref);
 		}
 	}
 	expand_refspecs();
@@ -129,8 +175,10 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
 			else
 				error("more than %d URL's specified, ignoring the rest", MAX_URI);
 		}
-		else if (is_refspec && !has_explicit_refspec)
-			add_refspec(xstrdup(s));
+		else if (is_refspec && !has_explicit_refspec) {
+			if (!wildcard_ref(s))
+				add_refspec(xstrdup(s));
+		}
 	}
 	fclose(f);
 	if (!n)
@@ -156,8 +204,10 @@ static int get_remote_config(const char* key, const char* value)
 				error("more than %d URL's specified, ignoring the rest", MAX_URI);
 		}
 		else if (config_get_refspecs &&
-			 !strcmp(key + 7 + config_repo_len, ".push"))
-			add_refspec(xstrdup(value));
+			 !strcmp(key + 7 + config_repo_len, ".push")) {
+			if (!wildcard_ref(value))
+				add_refspec(xstrdup(value));
+		}
 		else if (config_get_receivepack &&
 			 !strcmp(key + 7 + config_repo_len, ".receivepack")) {
 			if (!receivepack) {