Skip to content

Commit 9437394

Browse files
committed
Merge branch 'cb/fetch-set-upstream'
"git fetch" learned "--set-upstream" option to help those who first clone from their private fork they intend to push to, add the true upstream via "git remote add" and then "git fetch" from it. * cb/fetch-set-upstream: pull, fetch: add --set-upstream option
2 parents af2b8fa + 24bc1a1 commit 9437394

File tree

4 files changed

+241
-1
lines changed

4 files changed

+241
-1
lines changed

Documentation/fetch-options.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ ifndef::git-pull[]
169169
Disable recursive fetching of submodules (this has the same effect as
170170
using the `--recurse-submodules=no` option).
171171

172+
--set-upstream::
173+
If the remote is fetched successfully, pull and add upstream
174+
(tracking) reference, used by argument-less
175+
linkgit:git-pull[1] and other commands. For more information,
176+
see `branch.<name>.merge` and `branch.<name>.remote` in
177+
linkgit:git-config[1].
178+
172179
--submodule-prefix=<path>::
173180
Prepend <path> to paths printed in informative messages
174181
such as "Fetching submodule foo". This option is used

builtin/fetch.c

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "packfile.h"
2424
#include "list-objects-filter-options.h"
2525
#include "commit-reach.h"
26+
#include "branch.h"
2627

2728
#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
2829

@@ -50,7 +51,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
5051
static int prune_tags = -1; /* unspecified */
5152
#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
5253

53-
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
54+
static int all, append, dry_run, force, keep, multiple, update_head_ok;
55+
static int verbosity, deepen_relative, set_upstream;
5456
static int progress = -1;
5557
static int enable_auto_gc = 1;
5658
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -123,6 +125,8 @@ static struct option builtin_fetch_options[] = {
123125
OPT__VERBOSITY(&verbosity),
124126
OPT_BOOL(0, "all", &all,
125127
N_("fetch from all remotes")),
128+
OPT_BOOL(0, "set-upstream", &set_upstream,
129+
N_("set upstream for git pull/fetch")),
126130
OPT_BOOL('a', "append", &append,
127131
N_("append to .git/FETCH_HEAD instead of overwriting")),
128132
OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
@@ -1367,6 +1371,51 @@ static int do_fetch(struct transport *transport,
13671371
retcode = 1;
13681372
goto cleanup;
13691373
}
1374+
1375+
if (set_upstream) {
1376+
struct branch *branch = branch_get("HEAD");
1377+
struct ref *rm;
1378+
struct ref *source_ref = NULL;
1379+
1380+
/*
1381+
* We're setting the upstream configuration for the
1382+
* current branch. The relevent upstream is the
1383+
* fetched branch that is meant to be merged with the
1384+
* current one, i.e. the one fetched to FETCH_HEAD.
1385+
*
1386+
* When there are several such branches, consider the
1387+
* request ambiguous and err on the safe side by doing
1388+
* nothing and just emit a warning.
1389+
*/
1390+
for (rm = ref_map; rm; rm = rm->next) {
1391+
if (!rm->peer_ref) {
1392+
if (source_ref) {
1393+
warning(_("multiple branch detected, incompatible with --set-upstream"));
1394+
goto skip;
1395+
} else {
1396+
source_ref = rm;
1397+
}
1398+
}
1399+
}
1400+
if (source_ref) {
1401+
if (!strcmp(source_ref->name, "HEAD") ||
1402+
starts_with(source_ref->name, "refs/heads/"))
1403+
install_branch_config(0,
1404+
branch->name,
1405+
transport->remote->name,
1406+
source_ref->name);
1407+
else if (starts_with(source_ref->name, "refs/remotes/"))
1408+
warning(_("not setting upstream for a remote remote-tracking branch"));
1409+
else if (starts_with(source_ref->name, "refs/tags/"))
1410+
warning(_("not setting upstream for a remote tag"));
1411+
else
1412+
warning(_("unknown branch type"));
1413+
} else {
1414+
warning(_("no source branch found.\n"
1415+
"you need to specify exactly one branch with the --set-upstream option."));
1416+
}
1417+
}
1418+
skip:
13701419
free_refs(ref_map);
13711420

13721421
/* if neither --no-tags nor --tags was specified, do automated tag

builtin/pull.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ static char *opt_refmap;
129129
static char *opt_ipv4;
130130
static char *opt_ipv6;
131131
static int opt_show_forced_updates = -1;
132+
static char *set_upstream;
132133

133134
static struct option pull_options[] = {
134135
/* Shared options */
@@ -243,6 +244,9 @@ static struct option pull_options[] = {
243244
PARSE_OPT_NOARG),
244245
OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
245246
N_("check for forced-updates on all updated branches")),
247+
OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
248+
N_("set upstream for git pull/fetch"),
249+
PARSE_OPT_NOARG),
246250

247251
OPT_END()
248252
};
@@ -556,6 +560,8 @@ static int run_fetch(const char *repo, const char **refspecs)
556560
argv_array_push(&args, "--show-forced-updates");
557561
else if (opt_show_forced_updates == 0)
558562
argv_array_push(&args, "--no-show-forced-updates");
563+
if (set_upstream)
564+
argv_array_push(&args, set_upstream);
559565

560566
if (repo) {
561567
argv_array_push(&args, repo);

t/t5553-set-upstream.sh

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#!/bin/sh
2+
3+
test_description='"git fetch/pull --set-upstream" basic tests.'
4+
. ./test-lib.sh
5+
6+
check_config () {
7+
printf "%s\n" "$2" "$3" >"expect.$1" &&
8+
{
9+
git config "branch.$1.remote" && git config "branch.$1.merge"
10+
} >"actual.$1" &&
11+
test_cmp "expect.$1" "actual.$1"
12+
}
13+
14+
check_config_missing () {
15+
test_expect_code 1 git config "branch.$1.remote" &&
16+
test_expect_code 1 git config "branch.$1.merge"
17+
}
18+
19+
clear_config () {
20+
for branch in "$@"; do
21+
test_might_fail git config --unset-all "branch.$branch.remote"
22+
test_might_fail git config --unset-all "branch.$branch.merge"
23+
done
24+
}
25+
26+
ensure_fresh_upstream () {
27+
rm -rf parent && git init --bare parent
28+
}
29+
30+
test_expect_success 'setup bare parent fetch' '
31+
ensure_fresh_upstream &&
32+
git remote add upstream parent
33+
'
34+
35+
test_expect_success 'setup commit on master and other fetch' '
36+
test_commit one &&
37+
git push upstream master &&
38+
git checkout -b other &&
39+
test_commit two &&
40+
git push upstream other
41+
'
42+
43+
# tests for fetch --set-upstream
44+
45+
test_expect_success 'fetch --set-upstream does not set upstream w/o branch' '
46+
clear_config master other &&
47+
git checkout master &&
48+
git fetch --set-upstream upstream &&
49+
check_config_missing master &&
50+
check_config_missing other
51+
'
52+
53+
test_expect_success 'fetch --set-upstream upstream master sets branch master but not other' '
54+
clear_config master other &&
55+
git fetch --set-upstream upstream master &&
56+
check_config master upstream refs/heads/master &&
57+
check_config_missing other
58+
'
59+
60+
test_expect_success 'fetch --set-upstream upstream other sets branch other' '
61+
clear_config master other &&
62+
git fetch --set-upstream upstream other &&
63+
check_config master upstream refs/heads/other &&
64+
check_config_missing other
65+
'
66+
67+
test_expect_success 'fetch --set-upstream master:other does not set the branch other2' '
68+
clear_config other2 &&
69+
git fetch --set-upstream upstream master:other2 &&
70+
check_config_missing other2
71+
'
72+
73+
test_expect_success 'fetch --set-upstream http://nosuchdomain.example.com fails with invalid url' '
74+
# master explicitly not cleared, we check that it is not touched from previous value
75+
clear_config other other2 &&
76+
test_must_fail git fetch --set-upstream http://nosuchdomain.example.com &&
77+
check_config master upstream refs/heads/other &&
78+
check_config_missing other &&
79+
check_config_missing other2
80+
'
81+
82+
test_expect_success 'fetch --set-upstream with valid URL sets upstream to URL' '
83+
clear_config other other2 &&
84+
url="file://'"$PWD"'" &&
85+
git fetch --set-upstream "$url" &&
86+
check_config master "$url" HEAD &&
87+
check_config_missing other &&
88+
check_config_missing other2
89+
'
90+
91+
# tests for pull --set-upstream
92+
93+
test_expect_success 'setup bare parent pull' '
94+
git remote rm upstream &&
95+
ensure_fresh_upstream &&
96+
git remote add upstream parent
97+
'
98+
99+
test_expect_success 'setup commit on master and other pull' '
100+
test_commit three &&
101+
git push --tags upstream master &&
102+
test_commit four &&
103+
git push upstream other
104+
'
105+
106+
test_expect_success 'pull --set-upstream upstream master sets branch master but not other' '
107+
clear_config master other &&
108+
git pull --set-upstream upstream master &&
109+
check_config master upstream refs/heads/master &&
110+
check_config_missing other
111+
'
112+
113+
test_expect_success 'pull --set-upstream master:other2 does not set the branch other2' '
114+
clear_config other2 &&
115+
git pull --set-upstream upstream master:other2 &&
116+
check_config_missing other2
117+
'
118+
119+
test_expect_success 'pull --set-upstream upstream other sets branch master' '
120+
clear_config master other &&
121+
git pull --set-upstream upstream other &&
122+
check_config master upstream refs/heads/other &&
123+
check_config_missing other
124+
'
125+
126+
test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
127+
clear_config three &&
128+
git pull --tags --set-upstream upstream three &&
129+
check_config_missing three
130+
'
131+
132+
test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails with invalid url' '
133+
# master explicitly not cleared, we check that it is not touched from previous value
134+
clear_config other other2 three &&
135+
test_must_fail git pull --set-upstream http://nosuchdomain.example.com &&
136+
check_config master upstream refs/heads/other &&
137+
check_config_missing other &&
138+
check_config_missing other2 &&
139+
check_config_missing three
140+
'
141+
142+
test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
143+
clear_config master other &&
144+
git pull --set-upstream upstream HEAD &&
145+
check_config master upstream HEAD &&
146+
git checkout other &&
147+
git pull --set-upstream upstream HEAD &&
148+
check_config other upstream HEAD
149+
'
150+
151+
test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
152+
clear_config master three &&
153+
git pull --set-upstream upstream master three &&
154+
check_config_missing master &&
155+
check_config_missing three
156+
'
157+
158+
test_expect_success 'pull --set-upstream with valid URL sets upstream to URL' '
159+
clear_config master other other2 &&
160+
git checkout master &&
161+
url="file://'"$PWD"'" &&
162+
git pull --set-upstream "$url" &&
163+
check_config master "$url" HEAD &&
164+
check_config_missing other &&
165+
check_config_missing other2
166+
'
167+
168+
test_expect_success 'pull --set-upstream with valid URL and branch sets branch' '
169+
clear_config master other other2 &&
170+
git checkout master &&
171+
url="file://'"$PWD"'" &&
172+
git pull --set-upstream "$url" master &&
173+
check_config master "$url" refs/heads/master &&
174+
check_config_missing other &&
175+
check_config_missing other2
176+
'
177+
178+
test_done

0 commit comments

Comments
 (0)