Skip to content

Commit a9f5a35

Browse files
peffgitster
authored andcommitted
remote: separate the concept of push and fetch mirrors
git-remote currently has one option, "--mirror", which sets up mirror configuration which can be used for either fetching or pushing. It looks like this: [remote "mirror"] url = wherever fetch = +refs/*:refs/* mirror = true However, a remote like this can be dangerous and confusing. Specifically: 1. If you issue the wrong command, it can be devastating. You are not likely to "push" when you meant to "fetch", but "git remote update" will try to fetch it, even if you intended the remote only for pushing. In either case, the results can be quite destructive. An unintended push will overwrite or delete remote refs, and an unintended fetch can overwrite local branches. 2. The tracking setup code can produce confusing results. The fetch refspec above means that "git checkout -b new master" will consider refs/heads/master to come from the remote "mirror", even if you only ever intend to push to the mirror. It will set up the "new" branch to track mirror's refs/heads/master. 3. The push code tries to opportunistically update tracking branches. If you "git push mirror foo:bar", it will see that we are updating mirror's refs/heads/bar, which corresponds to our local refs/heads/bar, and will update our local branch. To solve this, we split the concept into "push mirrors" and "fetch mirrors". Push mirrors set only remote.*.mirror, solving (2) and (3), and making an accidental fetch write only into FETCH_HEAD. Fetch mirrors set only the fetch refspec, meaning an accidental push will not force-overwrite or delete refs on the remote end. The new syntax is "--mirror=<fetch|push>". For compatibility, we keep "--mirror" as-is, setting up both types simultaneously. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 13fc2c1 commit a9f5a35

File tree

3 files changed

+129
-19
lines changed

3 files changed

+129
-19
lines changed

Documentation/git-remote.txt

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ SYNOPSIS
1010
--------
1111
[verse]
1212
'git remote' [-v | --verbose]
13-
'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror] <name> <url>
13+
'git remote add' [-t <branch>] [-m <master>] [-f] [--tags|--no-tags] [--mirror=<fetch|push>] <name> <url>
1414
'git remote rename' <old> <new>
1515
'git remote rm' <name>
1616
'git remote set-head' <name> (-a | -d | <branch>)
@@ -67,11 +67,18 @@ multiple branches without grabbing all branches.
6767
With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
6868
up to point at remote's `<master>` branch. See also the set-head command.
6969
+
70-
In mirror mode, enabled with `\--mirror`, the refs will not be stored
71-
in the 'refs/remotes/' namespace, but in 'refs/heads/'. This option
72-
only makes sense in bare repositories. If a remote uses mirror
73-
mode, furthermore, `git push` will always behave as if `\--mirror`
74-
was passed.
70+
When a fetch mirror is created with `\--mirror=fetch`, the refs will not
71+
be stored in the 'refs/remotes/' namespace, but rather everything in
72+
'refs/' on the remote will be directly mirrored into 'refs/' in the
73+
local repository. This option only makes sense in bare repositories,
74+
because a fetch would overwrite any local commits.
75+
+
76+
When a push mirror is created with `\--mirror=push`, then `git push`
77+
will always behave as if `\--mirror` was passed.
78+
+
79+
The option `\--mirror` (with no type) sets up both push and fetch
80+
mirror configuration. It is kept for historical purposes, and is
81+
probably not what you want.
7582

7683
'rename'::
7784

builtin/remote.c

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
static const char * const builtin_remote_usage[] = {
1111
"git remote [-v | --verbose]",
12-
"git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
12+
"git remote add [-t <branch>] [-m <master>] [-f] [--mirror=<fetch|push>] <name> <url>",
1313
"git remote rename <old> <new>",
1414
"git remote rm <name>",
1515
"git remote set-head <name> (-a | -d | <branch>)",
@@ -117,6 +117,11 @@ enum {
117117
TAGS_SET = 2
118118
};
119119

120+
#define MIRROR_NONE 0
121+
#define MIRROR_FETCH 1
122+
#define MIRROR_PUSH 2
123+
#define MIRROR_BOTH (MIRROR_FETCH|MIRROR_PUSH)
124+
120125
static int add_branch(const char *key, const char *branchname,
121126
const char *remotename, int mirror, struct strbuf *tmp)
122127
{
@@ -131,9 +136,26 @@ static int add_branch(const char *key, const char *branchname,
131136
return git_config_set_multivar(key, tmp->buf, "^$", 0);
132137
}
133138

139+
static int parse_mirror_opt(const struct option *opt, const char *arg, int not)
140+
{
141+
unsigned *mirror = opt->value;
142+
if (not)
143+
*mirror = MIRROR_NONE;
144+
else if (!arg)
145+
*mirror = MIRROR_BOTH;
146+
else if (!strcmp(arg, "fetch"))
147+
*mirror = MIRROR_FETCH;
148+
else if (!strcmp(arg, "push"))
149+
*mirror = MIRROR_PUSH;
150+
else
151+
return error("unknown mirror argument: %s", arg);
152+
return 0;
153+
}
154+
134155
static int add(int argc, const char **argv)
135156
{
136-
int fetch = 0, mirror = 0, fetch_tags = TAGS_DEFAULT;
157+
int fetch = 0, fetch_tags = TAGS_DEFAULT;
158+
unsigned mirror = MIRROR_NONE;
137159
struct string_list track = STRING_LIST_INIT_NODUP;
138160
const char *master = NULL;
139161
struct remote *remote;
@@ -151,7 +173,9 @@ static int add(int argc, const char **argv)
151173
OPT_CALLBACK('t', "track", &track, "branch",
152174
"branch(es) to track", opt_parse_track),
153175
OPT_STRING('m', "master", &master, "branch", "master branch"),
154-
OPT_BOOLEAN(0, "mirror", &mirror, "no separate remotes"),
176+
{ OPTION_CALLBACK, 0, "mirror", &mirror, "push|fetch",
177+
"set up remote as a mirror to push to or fetch from",
178+
PARSE_OPT_OPTARG, parse_mirror_opt },
155179
OPT_END()
156180
};
157181

@@ -182,18 +206,19 @@ static int add(int argc, const char **argv)
182206
if (git_config_set(buf.buf, url))
183207
return 1;
184208

185-
strbuf_reset(&buf);
186-
strbuf_addf(&buf, "remote.%s.fetch", name);
187-
188-
if (track.nr == 0)
189-
string_list_append(&track, "*");
190-
for (i = 0; i < track.nr; i++) {
191-
if (add_branch(buf.buf, track.items[i].string,
192-
name, mirror, &buf2))
193-
return 1;
209+
if (!mirror || mirror & MIRROR_FETCH) {
210+
strbuf_reset(&buf);
211+
strbuf_addf(&buf, "remote.%s.fetch", name);
212+
if (track.nr == 0)
213+
string_list_append(&track, "*");
214+
for (i = 0; i < track.nr; i++) {
215+
if (add_branch(buf.buf, track.items[i].string,
216+
name, mirror, &buf2))
217+
return 1;
218+
}
194219
}
195220

196-
if (mirror) {
221+
if (mirror & MIRROR_PUSH) {
197222
strbuf_reset(&buf);
198223
strbuf_addf(&buf, "remote.%s.mirror", name);
199224
if (git_config_set(buf.buf, "true"))

t/t5505-remote.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,84 @@ test_expect_success 'add --mirror && prune' '
304304
git rev-parse --verify refs/heads/side)
305305
'
306306

307+
test_expect_success 'add --mirror=fetch' '
308+
mkdir mirror-fetch &&
309+
git init mirror-fetch/parent &&
310+
(cd mirror-fetch/parent &&
311+
test_commit one) &&
312+
git init --bare mirror-fetch/child &&
313+
(cd mirror-fetch/child &&
314+
git remote add --mirror=fetch -f parent ../parent)
315+
'
316+
317+
test_expect_success 'fetch mirrors act as mirrors during fetch' '
318+
(cd mirror-fetch/parent &&
319+
git branch new &&
320+
git branch -m master renamed
321+
) &&
322+
(cd mirror-fetch/child &&
323+
git fetch parent &&
324+
git rev-parse --verify refs/heads/new &&
325+
git rev-parse --verify refs/heads/renamed
326+
)
327+
'
328+
329+
test_expect_success 'fetch mirrors can prune' '
330+
(cd mirror-fetch/child &&
331+
git remote prune parent &&
332+
test_must_fail git rev-parse --verify refs/heads/master
333+
)
334+
'
335+
336+
test_expect_success 'fetch mirrors do not act as mirrors during push' '
337+
(cd mirror-fetch/parent &&
338+
git checkout HEAD^0
339+
) &&
340+
(cd mirror-fetch/child &&
341+
git branch -m renamed renamed2 &&
342+
git push parent
343+
) &&
344+
(cd mirror-fetch/parent &&
345+
git rev-parse --verify renamed &&
346+
test_must_fail git rev-parse --verify refs/heads/renamed2
347+
)
348+
'
349+
350+
test_expect_success 'add --mirror=push' '
351+
mkdir mirror-push &&
352+
git init --bare mirror-push/public &&
353+
git init mirror-push/private &&
354+
(cd mirror-push/private &&
355+
test_commit one &&
356+
git remote add --mirror=push public ../public
357+
)
358+
'
359+
360+
test_expect_success 'push mirrors act as mirrors during push' '
361+
(cd mirror-push/private &&
362+
git branch new &&
363+
git branch -m master renamed &&
364+
git push public
365+
) &&
366+
(cd mirror-push/private &&
367+
git rev-parse --verify refs/heads/new &&
368+
git rev-parse --verify refs/heads/renamed &&
369+
test_must_fail git rev-parse --verify refs/heads/master
370+
)
371+
'
372+
373+
test_expect_success 'push mirrors do not act as mirrors during fetch' '
374+
(cd mirror-push/public &&
375+
git branch -m renamed renamed2 &&
376+
git symbolic-ref HEAD refs/heads/renamed2
377+
) &&
378+
(cd mirror-push/private &&
379+
git fetch public &&
380+
git rev-parse --verify refs/heads/renamed &&
381+
test_must_fail git rev-parse --verify refs/heads/renamed2
382+
)
383+
'
384+
307385
test_expect_success 'add alt && prune' '
308386
(mkdir alttst &&
309387
cd alttst &&

0 commit comments

Comments
 (0)