Commits

Anonymous committed cea667e Merge

Merge branch 'jk/maint-remote-mirror-safer'

* jk/maint-remote-mirror-safer:
remote: deprecate --mirror
remote: separate the concept of push and fetch mirrors
remote: disallow some nonsensical option combinations

  • Participants
  • Parent commits 7e3ead1, 0990248

Comments (0)

Files changed (3)

Documentation/git-remote.txt

 --------
 [verse]
 'git remote' [-v | --verbose]
-'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url>
+'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>
 'git remote rename' <old> <new>
 'git remote rm' <name>
 'git remote set-head' <name> (-a | -d | <branch>)
 With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
 up to point at remote's `<master>` branch. See also the set-head command.
 +
-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.  If a remote uses mirror
-mode, furthermore, `git push` will always behave as if `\--mirror`
-was passed.
+When a fetch mirror is created with `\--mirror=fetch`, the refs will not
+be stored in the 'refs/remotes/' namespace, but rather everything in
+'refs/' on the remote will be directly mirrored into 'refs/' in the
+local repository. This option only makes sense in bare repositories,
+because a fetch would overwrite any local commits.
++
+When a push mirror is created with `\--mirror=push`, then `git push`
+will always behave as if `\--mirror` was passed.
 
 'rename'::
 
 
 static const char * const builtin_remote_usage[] = {
 	"git remote [-v | --verbose]",
-	"git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
+	"git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
 	"git remote rename <old> <new>",
 	"git remote rm <name>",
 	"git remote set-head <name> (-a | -d | <branch>)",
 	TAGS_SET = 2
 };
 
+#define MIRROR_NONE 0
+#define MIRROR_FETCH 1
+#define MIRROR_PUSH 2
+#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH)
+
 static int add_branch(const char *key, const char *branchname,
 		const char *remotename, int mirror, struct strbuf *tmp)
 {
 	return git_config_set_multivar(key, tmp->buf, "^$", 0);
 }
 
+static const char mirror_advice[] =
+"--mirror is dangerous and deprecated; please\n"
+"\t use --mirror=fetch or --mirror=push instead";
+
+static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
+{
+	unsigned *mirror = opt->value;
+	if (not)
+		*mirror = MIRROR_NONE;
+	else if (!arg) {
+		warning("%s", mirror_advice);
+		*mirror = MIRROR_BOTH;
+	}
+	else if (!strcmp(arg, "fetch"))
+		*mirror = MIRROR_FETCH;
+	else if (!strcmp(arg, "push"))
+		*mirror = MIRROR_PUSH;
+	else
+		return error("unknown mirror argument: %s", arg);
+	return 0;
+}
+
 static int add(int argc, const char **argv)
 {
-	int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT;
+	int fetch = 0, fetch_tags = TAGS_DEFAULT;
+	unsigned mirror = MIRROR_NONE;
 	struct string_list track = STRING_LIST_INIT_NODUP;
 	const char *master = NULL;
 	struct remote *remote;
 		OPT_CALLBACK('t', "track", &track, "branch",
 			"branch(es) to track", opt_parse_track),
 		OPT_STRING('m', "master", &master, "branch", "master branch"),
-		OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
+		{ OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
+			"set up remote as a mirror to push to or fetch from",
+			PARSE_OPT_OPTARG, parse_mirror_opt },
 		OPT_END()
 	};
 
 	if (argc < 2)
 		usage_with_options(builtin_remote_add_usage, options);
 
+	if (mirror && master)
+		die("specifying a master branch makes no sense with --mirror");
+	if (mirror && track.nr)
+		die("specifying branches to track makes no sense with --mirror");
+
 	name = argv[0];
 	url = argv[1];
 
 	if (git_config_set(buf.buf, url))
 		return 1;
 
-	strbuf_reset(&buf);
-	strbuf_addf(&buf, "remote.%s.fetch", name);
-
-	if (track.nr == 0)
-		string_list_append(&track, "*");
-	for (i = 0; i < track.nr; i++) {
-		if (add_branch(buf.buf, track.items[i].string,
-				name, mirror, &buf2))
-			return 1;
+	if (!mirror || mirror & MIRROR_FETCH) {
+		strbuf_reset(&buf);
+		strbuf_addf(&buf, "remote.%s.fetch", name);
+		if (track.nr == 0)
+			string_list_append(&track, "*");
+		for (i = 0; i < track.nr; i++) {
+			if (add_branch(buf.buf, track.items[i].string,
+				       name, mirror, &buf2))
+				return 1;
+		}
 	}
 
-	if (mirror) {
+	if (mirror & MIRROR_PUSH) {
 		strbuf_reset(&buf);
 		strbuf_addf(&buf, "remote.%s.mirror", name);
 		if (git_config_set(buf.buf, "true"))

t/t5505-remote.sh

 	 git rev-parse --verify refs/heads/side)
 '
 
+test_expect_success 'add --mirror=fetch' '
+	mkdir mirror-fetch &&
+	git init mirror-fetch/parent &&
+	(cd mirror-fetch/parent &&
+	 test_commit one) &&
+	git init --bare mirror-fetch/child &&
+	(cd mirror-fetch/child &&
+	 git remote add --mirror=fetch -f parent ../parent)
+'
+
+test_expect_success 'fetch mirrors act as mirrors during fetch' '
+	(cd mirror-fetch/parent &&
+	 git branch new &&
+	 git branch -m master renamed
+	) &&
+	(cd mirror-fetch/child &&
+	 git fetch parent &&
+	 git rev-parse --verify refs/heads/new &&
+	 git rev-parse --verify refs/heads/renamed
+	)
+'
+
+test_expect_success 'fetch mirrors can prune' '
+	(cd mirror-fetch/child &&
+	 git remote prune parent &&
+	 test_must_fail git rev-parse --verify refs/heads/master
+	)
+'
+
+test_expect_success 'fetch mirrors do not act as mirrors during push' '
+	(cd mirror-fetch/parent &&
+	 git checkout HEAD^0
+	) &&
+	(cd mirror-fetch/child &&
+	 git branch -m renamed renamed2 &&
+	 git push parent
+	) &&
+	(cd mirror-fetch/parent &&
+	 git rev-parse --verify renamed &&
+	 test_must_fail git rev-parse --verify refs/heads/renamed2
+	)
+'
+
+test_expect_success 'add --mirror=push' '
+	mkdir mirror-push &&
+	git init --bare mirror-push/public &&
+	git init mirror-push/private &&
+	(cd mirror-push/private &&
+	 test_commit one &&
+	 git remote add --mirror=push public ../public
+	)
+'
+
+test_expect_success 'push mirrors act as mirrors during push' '
+	(cd mirror-push/private &&
+	 git branch new &&
+	 git branch -m master renamed &&
+	 git push public
+	) &&
+	(cd mirror-push/private &&
+	 git rev-parse --verify refs/heads/new &&
+	 git rev-parse --verify refs/heads/renamed &&
+	 test_must_fail git rev-parse --verify refs/heads/master
+	)
+'
+
+test_expect_success 'push mirrors do not act as mirrors during fetch' '
+	(cd mirror-push/public &&
+	 git branch -m renamed renamed2 &&
+	 git symbolic-ref HEAD refs/heads/renamed2
+	) &&
+	(cd mirror-push/private &&
+	 git fetch public &&
+	 git rev-parse --verify refs/heads/renamed &&
+	 test_must_fail git rev-parse --verify refs/heads/renamed2
+	)
+'
+
 test_expect_success 'add alt && prune' '
 	(mkdir alttst &&
 	 cd alttst &&