Add a remote.*.mirror configuration option

This patch adds a remote.*.mirror configuration option that,
when set, automatically puts git-push in --mirror mode for that
remote.

Furthermore, the option is set automatically by `git remote
add --mirror'.

The code in remote.c to parse remote.*.skipdefaultupdate
had a subtle problem: a comment in the code indicated that
special care was needed for boolean options, but this care was
not used in parsing the option.  Since I was touching related
code, I did this fix too.

[jc: and I further fixed up the "ignore boolean" code.]

Signed-off-by: Paolo Bonzini <bonzini@gnu.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
diff --git a/Documentation/config.txt b/Documentation/config.txt
index fe43b12..03f1c3f 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -910,6 +910,10 @@
 	The default set of "refspec" for linkgit:git-push[1]. See
 	linkgit:git-push[1].
 
+remote.<name>.mirror::
+	If true, pushing to this remote will automatically behave
+	as if the `\--mirror` option was given on the command line.
+
 remote.<name>.skipDefaultUpdate::
 	If true, this remote will be skipped by default when updating
 	using the update subcommand of linkgit:git-remote[1].
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 3128170..dc1d4b0 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -69,7 +69,9 @@
 	be mirrored to the remote repository.  Newly created local
 	refs will be pushed to the remote end, locally updated refs
 	will be force updated on the remote end, and deleted refs
-	will be removed from the remote end.
+	will be removed from the remote end.  This is the default
+	if the configuration option `remote.<remote>.mirror` is
+	set.
 
 \--dry-run::
 	Do everything except actually send the updates.
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index 2cbd1f7..b20e851 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -47,9 +47,11 @@
 up to point at remote's `<master>` branch instead of whatever
 branch the `HEAD` at the remote repository actually points at.
 +
-In mirror mode, enabled with `--mirror`, the refs will not be stored
+In mirror mode, enabled with `\--mirror`, the refs will not be stored
 in the 'refs/remotes/' namespace, but in 'refs/heads/'.  This option
-only makes sense in bare repositories.
+only makes sense in bare repositories.  If a remote uses mirror
+mode, furthermore, `git push` will always behave as if `\--mirror`
+was passed.
 
 'rm'::
 
diff --git a/builtin-push.c b/builtin-push.c
index b68c681..b35aad6 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -56,6 +56,17 @@
 	if (!remote)
 		die("bad repository '%s'", repo);
 
+	if (remote->mirror)
+		flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+	if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
+		return -1;
+
+	if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
+				(TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
+		return error("--all and --mirror are incompatible");
+	}
+
 	if (!refspec
 		&& !(flags & TRANSPORT_PUSH_ALL)
 		&& remote->push_refspec_nr) {
@@ -95,6 +106,7 @@
 	int dry_run = 0;
 	int force = 0;
 	int tags = 0;
+	int rc;
 	const char *repo = NULL;	/* default repository */
 
 	struct option options[] = {
@@ -130,14 +142,10 @@
 		repo = argv[0];
 		set_refspecs(argv + 1, argc - 1);
 	}
-	if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) && refspec)
-		usage_with_options(push_usage, options);
 
-	if ((flags & (TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) ==
-				(TRANSPORT_PUSH_ALL|TRANSPORT_PUSH_MIRROR)) {
-		error("--all and --mirror are incompatible");
+	rc = do_push(repo, flags);
+	if (rc == -1)
 		usage_with_options(push_usage, options);
-	}
-
-	return do_push(repo, flags);
+	else
+		return rc;
 }
diff --git a/builtin-remote.c b/builtin-remote.c
index a3ee1ac..9d4432b 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -117,6 +117,13 @@
 			return 1;
 	}
 
+	if (mirror) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "remote.%s.mirror", name);
+		if (git_config_set(buf.buf, "yes"))
+			return 1;
+	}
+
 	if (fetch && fetch_remote(name))
 		return 1;
 
diff --git a/remote.c b/remote.c
index 06ad156..65ff414 100644
--- a/remote.c
+++ b/remote.c
@@ -337,44 +337,49 @@
 		return 0;
 	}
 	remote = make_remote(name, subkey - name);
-	if (!value) {
-		/* if we ever have a boolean variable, e.g. "remote.*.disabled"
-		 * [remote "frotz"]
-		 *      disabled
-		 * is a valid way to set it to true; we get NULL in value so
-		 * we need to handle it here.
-		 *
-		 * if (!strcmp(subkey, ".disabled")) {
-		 *      val = git_config_bool(key, value);
-		 *      return 0;
-		 * } else
-		 *
-		 */
-		return 0; /* ignore unknown booleans */
-	}
-	if (!strcmp(subkey, ".url")) {
-		add_url(remote, xstrdup(value));
+	if (!strcmp(subkey, ".mirror"))
+		remote->mirror = git_config_bool(key, value);
+	else if (!strcmp(subkey, ".skipdefaultupdate"))
+		remote->skip_default_update = git_config_bool(key, value);
+
+	else if (!strcmp(subkey, ".url")) {
+		const char *v;
+		if (git_config_string(&v, key, value))
+			return -1;
+		add_url(remote, v);
 	} else if (!strcmp(subkey, ".push")) {
-		add_push_refspec(remote, xstrdup(value));
+		const char *v;
+		if (git_config_string(&v, key, value))
+			return -1;
+		add_push_refspec(remote, v);
 	} else if (!strcmp(subkey, ".fetch")) {
-		add_fetch_refspec(remote, xstrdup(value));
+		const char *v;
+		if (git_config_string(&v, key, value))
+			return -1;
+		add_fetch_refspec(remote, v);
 	} else if (!strcmp(subkey, ".receivepack")) {
+		const char *v;
+		if (git_config_string(&v, key, value))
+			return -1;
 		if (!remote->receivepack)
-			remote->receivepack = xstrdup(value);
+			remote->receivepack = v;
 		else
 			error("more than one receivepack given, using the first");
 	} else if (!strcmp(subkey, ".uploadpack")) {
+		const char *v;
+		if (git_config_string(&v, key, value))
+			return -1;
 		if (!remote->uploadpack)
-			remote->uploadpack = xstrdup(value);
+			remote->uploadpack = v;
 		else
 			error("more than one uploadpack given, using the first");
 	} else if (!strcmp(subkey, ".tagopt")) {
 		if (!strcmp(value, "--no-tags"))
 			remote->fetch_tags = -1;
 	} else if (!strcmp(subkey, ".proxy")) {
-		remote->http_proxy = xstrdup(value);
-	} else if (!strcmp(subkey, ".skipdefaultupdate"))
-		remote->skip_default_update = 1;
+		return git_config_string((const char **)&remote->http_proxy,
+					 key, value);
+	}
 	return 0;
 }
 
diff --git a/remote.h b/remote.h
index a38774b..6878c52 100644
--- a/remote.h
+++ b/remote.h
@@ -26,6 +26,7 @@
 	 */
 	int fetch_tags;
 	int skip_default_update;
+	int mirror;
 
 	const char *receivepack;
 	const char *uploadpack;
diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh
index ed3fec1..ea49ded 100755
--- a/t/t5517-push-mirror.sh
+++ b/t/t5517-push-mirror.sh
@@ -25,7 +25,7 @@
 	(
 		cd master &&
 		git init &&
-		git config remote.up.url ../mirror
+		git remote add $1 up ../mirror
 	)
 }
 
@@ -225,4 +225,43 @@
 
 '
 
+test_expect_success 'remote.foo.mirror adds and removes branches' '
+
+	mk_repo_pair --mirror &&
+	(
+		cd master &&
+		echo one >foo && git add foo && git commit -m one &&
+		git branch keep master &&
+		git branch remove master &&
+		git push up &&
+		git branch -D remove
+		git push up
+	) &&
+	(
+		cd mirror &&
+		git show-ref -s --verify refs/heads/keep &&
+		invert git show-ref -s --verify refs/heads/remove
+	)
+
+'
+
+test_expect_success 'remote.foo.mirror=no has no effect' '
+
+	mk_repo_pair &&
+	(
+		cd master &&
+		echo one >foo && git add foo && git commit -m one &&
+		git config --add remote.up.mirror no &&
+		git branch keep master &&
+		git push --mirror up &&
+		git branch -D keep &&
+		git push up
+	) &&
+	(
+		cd mirror &&
+		git show-ref -s --verify refs/heads/keep
+	)
+
+'
+
 test_done