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>
			
			
				maint
			
			
		
							parent
							
								
									ff66981f45
								
							
						
					
					
						commit
						24bc1a1292
					
				| 
						 | 
					@ -169,6 +169,13 @@ ifndef::git-pull[]
 | 
				
			||||||
	Disable recursive fetching of submodules (this has the same effect as
 | 
						Disable recursive fetching of submodules (this has the same effect as
 | 
				
			||||||
	using the `--recurse-submodules=no` option).
 | 
						using the `--recurse-submodules=no` option).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					--set-upstream::
 | 
				
			||||||
 | 
						If the remote is fetched successfully, pull and add upstream
 | 
				
			||||||
 | 
						(tracking) reference, used by argument-less
 | 
				
			||||||
 | 
						linkgit:git-pull[1] and other commands. For more information,
 | 
				
			||||||
 | 
						see `branch.<name>.merge` and `branch.<name>.remote` in
 | 
				
			||||||
 | 
						linkgit:git-config[1].
 | 
				
			||||||
 | 
					
 | 
				
			||||||
--submodule-prefix=<path>::
 | 
					--submodule-prefix=<path>::
 | 
				
			||||||
	Prepend <path> to paths printed in informative messages
 | 
						Prepend <path> to paths printed in informative messages
 | 
				
			||||||
	such as "Fetching submodule foo".  This option is used
 | 
						such as "Fetching submodule foo".  This option is used
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@
 | 
				
			||||||
#include "packfile.h"
 | 
					#include "packfile.h"
 | 
				
			||||||
#include "list-objects-filter-options.h"
 | 
					#include "list-objects-filter-options.h"
 | 
				
			||||||
#include "commit-reach.h"
 | 
					#include "commit-reach.h"
 | 
				
			||||||
 | 
					#include "branch.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 | 
					#define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,7 +51,8 @@ static int fetch_prune_tags_config = -1; /* unspecified */
 | 
				
			||||||
static int prune_tags = -1; /* unspecified */
 | 
					static int prune_tags = -1; /* unspecified */
 | 
				
			||||||
#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 | 
					#define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative;
 | 
					static int all, append, dry_run, force, keep, multiple, update_head_ok;
 | 
				
			||||||
 | 
					static int verbosity, deepen_relative, set_upstream;
 | 
				
			||||||
static int progress = -1;
 | 
					static int progress = -1;
 | 
				
			||||||
static int enable_auto_gc = 1;
 | 
					static int enable_auto_gc = 1;
 | 
				
			||||||
static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
 | 
					static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen;
 | 
				
			||||||
| 
						 | 
					@ -123,6 +125,8 @@ static struct option builtin_fetch_options[] = {
 | 
				
			||||||
	OPT__VERBOSITY(&verbosity),
 | 
						OPT__VERBOSITY(&verbosity),
 | 
				
			||||||
	OPT_BOOL(0, "all", &all,
 | 
						OPT_BOOL(0, "all", &all,
 | 
				
			||||||
		 N_("fetch from all remotes")),
 | 
							 N_("fetch from all remotes")),
 | 
				
			||||||
 | 
						OPT_BOOL(0, "set-upstream", &set_upstream,
 | 
				
			||||||
 | 
							 N_("set upstream for git pull/fetch")),
 | 
				
			||||||
	OPT_BOOL('a', "append", &append,
 | 
						OPT_BOOL('a', "append", &append,
 | 
				
			||||||
		 N_("append to .git/FETCH_HEAD instead of overwriting")),
 | 
							 N_("append to .git/FETCH_HEAD instead of overwriting")),
 | 
				
			||||||
	OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
 | 
						OPT_STRING(0, "upload-pack", &upload_pack, N_("path"),
 | 
				
			||||||
| 
						 | 
					@ -1367,6 +1371,51 @@ static int do_fetch(struct transport *transport,
 | 
				
			||||||
		retcode = 1;
 | 
							retcode = 1;
 | 
				
			||||||
		goto cleanup;
 | 
							goto cleanup;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (set_upstream) {
 | 
				
			||||||
 | 
							struct branch *branch = branch_get("HEAD");
 | 
				
			||||||
 | 
							struct ref *rm;
 | 
				
			||||||
 | 
							struct ref *source_ref = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/*
 | 
				
			||||||
 | 
							 * We're setting the upstream configuration for the
 | 
				
			||||||
 | 
							 * current branch. The relevent upstream is the
 | 
				
			||||||
 | 
							 * fetched branch that is meant to be merged with the
 | 
				
			||||||
 | 
							 * current one, i.e. the one fetched to FETCH_HEAD.
 | 
				
			||||||
 | 
							 *
 | 
				
			||||||
 | 
							 * When there are several such branches, consider the
 | 
				
			||||||
 | 
							 * request ambiguous and err on the safe side by doing
 | 
				
			||||||
 | 
							 * nothing and just emit a warning.
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							for (rm = ref_map; rm; rm = rm->next) {
 | 
				
			||||||
 | 
								if (!rm->peer_ref) {
 | 
				
			||||||
 | 
									if (source_ref) {
 | 
				
			||||||
 | 
										warning(_("multiple branch detected, incompatible with --set-upstream"));
 | 
				
			||||||
 | 
										goto skip;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										source_ref = rm;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (source_ref) {
 | 
				
			||||||
 | 
								if (!strcmp(source_ref->name, "HEAD") ||
 | 
				
			||||||
 | 
								    starts_with(source_ref->name, "refs/heads/"))
 | 
				
			||||||
 | 
									install_branch_config(0,
 | 
				
			||||||
 | 
											      branch->name,
 | 
				
			||||||
 | 
											      transport->remote->name,
 | 
				
			||||||
 | 
											      source_ref->name);
 | 
				
			||||||
 | 
								else if (starts_with(source_ref->name, "refs/remotes/"))
 | 
				
			||||||
 | 
									warning(_("not setting upstream for a remote remote-tracking branch"));
 | 
				
			||||||
 | 
								else if (starts_with(source_ref->name, "refs/tags/"))
 | 
				
			||||||
 | 
									warning(_("not setting upstream for a remote tag"));
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									warning(_("unknown branch type"));
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								warning(_("no source branch found.\n"
 | 
				
			||||||
 | 
									"you need to specify exactly one branch with the --set-upstream option."));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					 skip:
 | 
				
			||||||
	free_refs(ref_map);
 | 
						free_refs(ref_map);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/* if neither --no-tags nor --tags was specified, do automated tag
 | 
						/* if neither --no-tags nor --tags was specified, do automated tag
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,6 +129,7 @@ static char *opt_refmap;
 | 
				
			||||||
static char *opt_ipv4;
 | 
					static char *opt_ipv4;
 | 
				
			||||||
static char *opt_ipv6;
 | 
					static char *opt_ipv6;
 | 
				
			||||||
static int opt_show_forced_updates = -1;
 | 
					static int opt_show_forced_updates = -1;
 | 
				
			||||||
 | 
					static char *set_upstream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static struct option pull_options[] = {
 | 
					static struct option pull_options[] = {
 | 
				
			||||||
	/* Shared options */
 | 
						/* Shared options */
 | 
				
			||||||
| 
						 | 
					@ -243,6 +244,9 @@ static struct option pull_options[] = {
 | 
				
			||||||
		PARSE_OPT_NOARG),
 | 
							PARSE_OPT_NOARG),
 | 
				
			||||||
	OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
 | 
						OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates,
 | 
				
			||||||
		 N_("check for forced-updates on all updated branches")),
 | 
							 N_("check for forced-updates on all updated branches")),
 | 
				
			||||||
 | 
						OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL,
 | 
				
			||||||
 | 
							N_("set upstream for git pull/fetch"),
 | 
				
			||||||
 | 
							PARSE_OPT_NOARG),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	OPT_END()
 | 
						OPT_END()
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					@ -556,6 +560,8 @@ static int run_fetch(const char *repo, const char **refspecs)
 | 
				
			||||||
		argv_array_push(&args, "--show-forced-updates");
 | 
							argv_array_push(&args, "--show-forced-updates");
 | 
				
			||||||
	else if (opt_show_forced_updates == 0)
 | 
						else if (opt_show_forced_updates == 0)
 | 
				
			||||||
		argv_array_push(&args, "--no-show-forced-updates");
 | 
							argv_array_push(&args, "--no-show-forced-updates");
 | 
				
			||||||
 | 
						if (set_upstream)
 | 
				
			||||||
 | 
							argv_array_push(&args, set_upstream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (repo) {
 | 
						if (repo) {
 | 
				
			||||||
		argv_array_push(&args, repo);
 | 
							argv_array_push(&args, repo);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,178 @@
 | 
				
			||||||
 | 
					#!/bin/sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_description='"git fetch/pull --set-upstream" basic tests.'
 | 
				
			||||||
 | 
					. ./test-lib.sh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					check_config () {
 | 
				
			||||||
 | 
						printf "%s\n" "$2" "$3" >"expect.$1" &&
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							git config "branch.$1.remote" && git config "branch.$1.merge"
 | 
				
			||||||
 | 
						} >"actual.$1" &&
 | 
				
			||||||
 | 
						test_cmp "expect.$1" "actual.$1"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					check_config_missing () {
 | 
				
			||||||
 | 
						test_expect_code 1 git config "branch.$1.remote" &&
 | 
				
			||||||
 | 
						test_expect_code 1 git config "branch.$1.merge"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					clear_config () {
 | 
				
			||||||
 | 
						for branch in "$@"; do
 | 
				
			||||||
 | 
							test_might_fail git config --unset-all "branch.$branch.remote"
 | 
				
			||||||
 | 
							test_might_fail git config --unset-all "branch.$branch.merge"
 | 
				
			||||||
 | 
						done
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ensure_fresh_upstream () {
 | 
				
			||||||
 | 
						rm -rf parent && git init --bare parent
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'setup bare parent fetch' '
 | 
				
			||||||
 | 
						ensure_fresh_upstream &&
 | 
				
			||||||
 | 
						git remote add upstream parent
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'setup commit on master and other fetch' '
 | 
				
			||||||
 | 
						test_commit one &&
 | 
				
			||||||
 | 
						git push upstream master &&
 | 
				
			||||||
 | 
						git checkout -b other &&
 | 
				
			||||||
 | 
						test_commit two &&
 | 
				
			||||||
 | 
						git push upstream other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# tests for fetch --set-upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream does not set upstream w/o branch' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git checkout master &&
 | 
				
			||||||
 | 
						git fetch --set-upstream upstream &&
 | 
				
			||||||
 | 
						check_config_missing master &&
 | 
				
			||||||
 | 
						check_config_missing other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream upstream master sets branch master but not other' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git fetch --set-upstream upstream master &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/master &&
 | 
				
			||||||
 | 
						check_config_missing other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream upstream other sets branch other' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git fetch --set-upstream upstream other &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/other &&
 | 
				
			||||||
 | 
						check_config_missing other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream master:other does not set the branch other2' '
 | 
				
			||||||
 | 
						clear_config other2 &&
 | 
				
			||||||
 | 
						git fetch --set-upstream upstream master:other2 &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream http://nosuchdomain.example.com fails with invalid url' '
 | 
				
			||||||
 | 
						# master explicitly not cleared, we check that it is not touched from previous value
 | 
				
			||||||
 | 
						clear_config other other2 &&
 | 
				
			||||||
 | 
						test_must_fail git fetch --set-upstream http://nosuchdomain.example.com &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/other &&
 | 
				
			||||||
 | 
						check_config_missing other &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'fetch --set-upstream with valid URL sets upstream to URL' '
 | 
				
			||||||
 | 
						clear_config other other2 &&
 | 
				
			||||||
 | 
						url="file://'"$PWD"'" &&
 | 
				
			||||||
 | 
						git fetch --set-upstream "$url" &&
 | 
				
			||||||
 | 
						check_config master "$url" HEAD &&
 | 
				
			||||||
 | 
						check_config_missing other &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# tests for pull --set-upstream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'setup bare parent pull' '
 | 
				
			||||||
 | 
						git remote rm upstream &&
 | 
				
			||||||
 | 
						ensure_fresh_upstream &&
 | 
				
			||||||
 | 
						git remote add upstream parent
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'setup commit on master and other pull' '
 | 
				
			||||||
 | 
						test_commit three &&
 | 
				
			||||||
 | 
						git push --tags upstream master &&
 | 
				
			||||||
 | 
						test_commit four &&
 | 
				
			||||||
 | 
						git push upstream other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream upstream master sets branch master but not other' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream master &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/master &&
 | 
				
			||||||
 | 
						check_config_missing other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream master:other2 does not set the branch other2' '
 | 
				
			||||||
 | 
						clear_config other2 &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream master:other2 &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream upstream other sets branch master' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream other &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/other &&
 | 
				
			||||||
 | 
						check_config_missing other
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream upstream tag does not set the tag' '
 | 
				
			||||||
 | 
						clear_config three &&
 | 
				
			||||||
 | 
						git pull --tags --set-upstream upstream three &&
 | 
				
			||||||
 | 
						check_config_missing three
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails with invalid url' '
 | 
				
			||||||
 | 
						# master explicitly not cleared, we check that it is not touched from previous value
 | 
				
			||||||
 | 
						clear_config other other2 three &&
 | 
				
			||||||
 | 
						test_must_fail git pull --set-upstream http://nosuchdomain.example.com &&
 | 
				
			||||||
 | 
						check_config master upstream refs/heads/other &&
 | 
				
			||||||
 | 
						check_config_missing other &&
 | 
				
			||||||
 | 
						check_config_missing other2 &&
 | 
				
			||||||
 | 
						check_config_missing three
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '
 | 
				
			||||||
 | 
						clear_config master other &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream HEAD &&
 | 
				
			||||||
 | 
						check_config master upstream HEAD &&
 | 
				
			||||||
 | 
						git checkout other &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream HEAD &&
 | 
				
			||||||
 | 
						check_config other upstream HEAD
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '
 | 
				
			||||||
 | 
						clear_config master three &&
 | 
				
			||||||
 | 
						git pull --set-upstream upstream master three &&
 | 
				
			||||||
 | 
						check_config_missing master &&
 | 
				
			||||||
 | 
						check_config_missing three
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream with valid URL sets upstream to URL' '
 | 
				
			||||||
 | 
						clear_config master other other2 &&
 | 
				
			||||||
 | 
						git checkout master &&
 | 
				
			||||||
 | 
						url="file://'"$PWD"'" &&
 | 
				
			||||||
 | 
						git pull --set-upstream "$url" &&
 | 
				
			||||||
 | 
						check_config master "$url" HEAD &&
 | 
				
			||||||
 | 
						check_config_missing other &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_expect_success 'pull --set-upstream with valid URL and branch sets branch' '
 | 
				
			||||||
 | 
						clear_config master other other2 &&
 | 
				
			||||||
 | 
						git checkout master &&
 | 
				
			||||||
 | 
						url="file://'"$PWD"'" &&
 | 
				
			||||||
 | 
						git pull --set-upstream "$url" master &&
 | 
				
			||||||
 | 
						check_config master "$url" refs/heads/master &&
 | 
				
			||||||
 | 
						check_config_missing other &&
 | 
				
			||||||
 | 
						check_config_missing other2
 | 
				
			||||||
 | 
					'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test_done
 | 
				
			||||||
		Loading…
	
		Reference in New Issue