pull, fetch: add --set-upstream option

Add the --set-upstream option to git pull/fetch which lets the user set the upstream configuration (branch.<current-branch-name>.merge and branch.<current-branch-name>.remote) for the current branch. A typical use-case is: git clone http://example.com/my-public-fork git remote add main http://example.com/project-main-repo git pull --set-upstream main master or, instead of the last line: git fetch --set-upstream main master git merge # or git rebase This is mostly equivalent to cloning project-main-repo (which sets upsteam) and then "git remote add" my-public-fork, but may feel more natural for people using a hosting system which allows forking from the web UI. This functionality is analog to "git push --set-upstream". Signed-off-by: Corentin BOMPARD <corentin.bompard@etu.univ-lyon1.fr> Signed-off-by: Nathan BERBEZIER <nathan.berbezier@etu.univ-lyon1.fr> Signed-off-by: Pablo CHABANNE <pablo.chabanne@etu.univ-lyon1.fr> Signed-off-by: Matthieu Moy <git@matthieu-moy.fr> Patch-edited-by: Matthieu Moy <git@matthieu-moy.fr> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Corentin BOMPARD committed Aug 19, 2019 at 11:11 UTC 24bc1a129260eaf713c87f087f28883fe18891b5
4 files changed +241 -1
Documentation/fetch-options.txt
+7
@@ -169,6 +169,13 @@ ifndef::git-pull[]
169 Disable recursive fetching of submodules (this has the same effect as
170 using the `--recurse-submodules=no` option).
171
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 +
179 --submodule-prefix=<path>::
180 Prepend <path> to paths printed in informative messages
181 such as "Fetching submodule foo". This option is used
builtin/fetch.c
+50 -1
@@ -23,6 +23,7 @@
23 #include "packfile.h"
24 #include "list-objects-filter-options.h"
25 #include "commit-reach.h"
26 +#include "branch.h"
27
28 #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
29
@@ -50,7 +51,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
51 static int prune_tags = -1; /* unspecified */
52 #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
53
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;
56 static int progress = -1;
57 static int enable_auto_gc = 1;
58 static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
@@ -123,6 +125,8 @@ static struct option builtin_fetch_options[] = {
125 OPT__VERBOSITY(&verbosity),
126 OPT_BOOL(0, "all", &all,
127 N_("fetch from all remotes")),
128 + OPT_BOOL(0, "set-upstream", &set_upstream,
129 + N_("set upstream for git pull/fetch")),
130 OPT_BOOL('a', "append", &append,
131 N_("append to .git/FETCH_HEAD instead of overwriting")),
132 OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
@@ -1367,6 +1371,51 @@ static int do_fetch(struct transport *transport,
1371 retcode = 1;
1372 goto cleanup;
1373 }
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:
1419 free_refs(ref_map);
1420
1421 /* if neither --no-tags nor --tags was specified, do automated tag
builtin/pull.c
+6
@@ -129,6 +129,7 @@ static char *opt_refmap;
129 static char *opt_ipv4;
130 static char *opt_ipv6;
131 static int opt_show_forced_updates = -1;
132 +static char *set_upstream;
133
134 static struct option pull_options[] = {
135 /* Shared options */
@@ -243,6 +244,9 @@ static struct option pull_options[] = {
244 PARSE_OPT_NOARG),
245 OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
246 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),
250
251 OPT_END()
252 };
@@ -556,6 +560,8 @@ static int run_fetch(const char *repo, const char **refspecs)
560 argv_array_push(&args, "--show-forced-updates");
561 else if (opt_show_forced_updates == 0)
562 argv_array_push(&args, "--no-show-forced-updates");
563 + if (set_upstream)
564 + argv_array_push(&args, set_upstream);
565
566 if (repo) {
567 argv_array_push(&args, repo);
t/t5553-set-upstream.sh new
+178
@@ -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