Git development
 help / color / mirror / Atom feed
From: Phillip Wood <phillip.wood123@gmail.com>
To: Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>,
	git@vger.kernel.org
Cc: Ramsay Jones <ramsay@ramsayjones.plus.com>,
	"D. Ben Knoble" <ben.knoble@gmail.com>,
	Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>,
	Marc Branchaud <marcnarc@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: Re: [PATCH v7] checkout: extend --track with a "fetch" mode to refresh start-point
Date: Mon, 11 May 2026 14:16:30 +0100	[thread overview]
Message-ID: <98410b9c-8ed6-41df-b508-ca9e15d4ee53@gmail.com> (raw)
In-Reply-To: <pull.2281.v7.git.git.1778280727849.gitgitgadget@gmail.com>

Hi Harald

On 08/05/2026 23:52, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
> 
> +static void fetch_remote_for_start_point(const char *arg)
> +{
> +	char *remote_name = NULL;
> +	char *src_ref = NULL;
> +	struct child_process cmd = CHILD_PROCESS_INIT;
> +	struct strbuf dst_ref = STRBUF_INIT;
> +	int have_existing_ref = 0;
> +
> +	if (resolve_fetch_target(arg, &remote_name, &src_ref))
> +		return;
> +
> +	if (src_ref) {
> +		const char *short_src = src_ref;
> +		struct object_id oid;
> +
> +		skip_prefix(short_src, "refs/heads/", &short_src);
> +		strbuf_addf(&dst_ref, "refs/remotes/%s/%s", remote_name, short_src);
> +		if (!refs_read_ref(get_main_ref_store(the_repository),
> +				   dst_ref.buf, &oid))
> +			have_existing_ref = 1;

src_ref is the name of the branch on the remote server, not the name of 
the remote tracking ref which is given by arg. If arg is a remote name 
then we need to resolve refs/remotes/$arg/HEAD to find the branch to 
check, otherwise we should be checking refs/remotes/$arg

I've only given this version a quick scan through, but I didn't notice 
any other issues.

Thanks

Phillip

> +	}
> +
> +	strvec_pushl(&cmd.args, "fetch", remote_name, NULL);
> +	if (src_ref)
> +		strvec_push(&cmd.args, src_ref);
> +	cmd.git_cmd = 1;
> +	if (run_command(&cmd)) {
> +		if (have_existing_ref)
> +			warning(_("failed to fetch start-point '%s'; "
> +				  "using existing '%s'"),
> +				arg, dst_ref.buf);
> +		else
> +			die(_("failed to fetch start-point '%s'"), arg);
> +	}
> +
> +	free(remote_name);
> +	free(src_ref);
> +	strbuf_release(&dst_ref);
> +}
> +
> +static int parse_opt_checkout_track(const struct option *opt,
> +				    const char *arg, int unset)
> +{
> +	struct checkout_opts *opts = opt->value;
> +	struct string_list tokens = STRING_LIST_INIT_DUP;
> +	struct string_list_item *item;
> +	int saw_direct = 0, saw_inherit = 0;
> +	int ret = 0;
> +
> +	opts->fetch = 0;
> +
> +	if (unset) {
> +		opts->track = BRANCH_TRACK_NEVER;
> +		return 0;
> +	}
> +
> +	opts->track = BRANCH_TRACK_EXPLICIT;
> +	if (!arg)
> +		return 0;
> +
> +	string_list_split(&tokens, arg, ",", -1);
> +	for_each_string_list_item(item, &tokens) {
> +		if (!strcmp(item->string, "fetch")) {
> +			opts->fetch = 1;
> +		} else if (!strcmp(item->string, "direct")) {
> +			saw_direct = 1;
> +			opts->track = BRANCH_TRACK_EXPLICIT;
> +		} else if (!strcmp(item->string, "inherit")) {
> +			saw_inherit = 1;
> +			opts->track = BRANCH_TRACK_INHERIT;
> +		} else {
> +			ret = error(_("option `%s' expects \"%s\", \"%s\", "
> +				      "or \"%s\""),
> +				    "--track", "direct", "inherit", "fetch");
> +			goto out;
> +		}
> +	}
> +
> +	if (saw_direct && saw_inherit)
> +		ret = error(_("option `%s' cannot combine \"%s\" and \"%s\""),
> +			    "--track", "direct", "inherit");
> +
> +out:
> +	string_list_clear(&tokens, 0);
> +	return ret;
> +}
> +
>   static void branch_info_release(struct branch_info *info)
>   {
>   	free(info->name);
> @@ -1237,7 +1391,6 @@ static int git_checkout_config(const char *var, const char *value,
>   		opts->dwim_new_local_branch = git_config_bool(var, value);
>   		return 0;
>   	}
> -
>   	if (starts_with(var, "submodule."))
>   		return git_default_submodule_config(var, value, NULL);
>   
> @@ -1734,10 +1887,10 @@ static struct option *add_common_switch_branch_options(
>   {
>   	struct option options[] = {
>   		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_CALLBACK_F('t', "track",  &opts->track, "(direct|inherit)",
> +		OPT_CALLBACK_F('t', "track",  opts, "(direct|inherit|fetch)[,...]",
>   			N_("set branch tracking configuration"),
>   			PARSE_OPT_OPTARG,
> -			parse_opt_tracking_mode),
> +			parse_opt_checkout_track),
>   		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
>   			   PARSE_OPT_NOCOMPLETE),
>   		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unborn branch")),
> @@ -1942,8 +2095,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
>   			opts->dwim_new_local_branch &&
>   			opts->track == BRANCH_TRACK_UNSPECIFIED &&
>   			!opts->new_branch;
> -		int n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
> -					     &new_branch_info, opts, &rev);
> +		int n;
> +
> +		if (opts->fetch)
> +			fetch_remote_for_start_point(argv[0]);
> +
> +		n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
> +					 &new_branch_info, opts, &rev);
>   		argv += n;
>   		argc -= n;
>   	} else if (!opts->accept_ref && opts->from_treeish) {
> diff --git a/t/t7201-co.sh b/t/t7201-co.sh
> index 9bcf7c0b40..19ac6a1a2e 100755
> --- a/t/t7201-co.sh
> +++ b/t/t7201-co.sh
> @@ -801,4 +801,136 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
>   	test_cmp_config "" --default "" branch.main2.merge
>   '
>   
> +test_expect_success 'setup upstream for --track=fetch tests' '
> +	git checkout main &&
> +	git init fetch_upstream &&
> +	test_commit -C fetch_upstream u_main &&
> +	git remote add fetch_upstream fetch_upstream &&
> +	git fetch fetch_upstream &&
> +	git -C fetch_upstream checkout -b fetch_new &&
> +	test_commit -C fetch_upstream u_new
> +'
> +
> +test_expect_success 'checkout --track=fetch -b picks up branch created upstream after clone' '
> +	git checkout main &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new &&
> +	git checkout --track=fetch -b local_new fetch_upstream/fetch_new &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD &&
> +	test_cmp_config fetch_upstream branch.local_new.remote &&
> +	test_cmp_config refs/heads/fetch_new branch.local_new.merge
> +'
> +
> +test_expect_success 'checkout --track=fetch <remote>/<branch> leaves other tracking branches untouched' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_target &&
> +	test_commit -C fetch_upstream u_target_pre &&
> +	git -C fetch_upstream checkout -b fetch_other &&
> +	test_commit -C fetch_upstream u_other_pre &&
> +	git fetch fetch_upstream &&
> +	other_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_other) &&
> +	git -C fetch_upstream checkout fetch_target &&
> +	test_commit -C fetch_upstream u_target_post &&
> +	git -C fetch_upstream checkout fetch_other &&
> +	test_commit -C fetch_upstream u_other_post &&
> +	git checkout --track=fetch -b local_target fetch_upstream/fetch_target &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_target HEAD &&
> +	test "$(git rev-parse refs/remotes/fetch_upstream/fetch_other)" = "$other_before"
> +'
> +
> +test_expect_success 'checkout --track=fetch with bare remote name fetches only <remote>/HEAD target' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout main &&
> +	git remote set-head fetch_upstream main &&
> +	git -C fetch_upstream checkout -b fetch_unrelated &&
> +	test_commit -C fetch_upstream u_unrelated_pre &&
> +	git fetch fetch_upstream fetch_unrelated &&
> +	unrelated_before=$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated) &&
> +	git -C fetch_upstream checkout main &&
> +	test_commit -C fetch_upstream u_main_post &&
> +	git -C fetch_upstream checkout fetch_unrelated &&
> +	test_commit -C fetch_upstream u_unrelated_post &&
> +	git checkout --track=fetch -b local_from_remote fetch_upstream &&
> +	test_cmp_rev refs/remotes/fetch_upstream/main HEAD &&
> +	test "$(git rev-parse refs/remotes/fetch_upstream/fetch_unrelated)" = "$unrelated_before"
> +'
> +
> +test_expect_success 'checkout --track=fetch aborts and does not create branch when no existing ref' '
> +	git checkout main &&
> +	test_might_fail git branch -D bogus &&
> +	test_must_fail git checkout --track=fetch -b bogus fetch_upstream/does_not_exist &&
> +	test_must_fail git rev-parse --verify refs/heads/bogus
> +'
> +
> +test_expect_success 'checkout --track=fetch warns and proceeds when fetch fails but ref exists' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_offline &&
> +	test_commit -C fetch_upstream u_offline &&
> +	git fetch fetch_upstream fetch_offline &&
> +	saved_url=$(git config remote.fetch_upstream.url) &&
> +	test_when_finished "git config remote.fetch_upstream.url \"$saved_url\"" &&
> +	git config remote.fetch_upstream.url ./does-not-exist &&
> +	git checkout --track=fetch -b local_offline fetch_upstream/fetch_offline 2>err &&
> +	test_grep "failed to fetch" err &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_offline HEAD
> +'
> +
> +test_expect_success 'checkout --track=fetch resolves through configured fetch refspec' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_refspec &&
> +	test_commit -C fetch_upstream u_refspec &&
> +	git fetch fetch_upstream fetch_refspec &&
> +	git remote add fetch_custom ./fetch_upstream &&
> +	test_when_finished "git remote remove fetch_custom" &&
> +	git config --replace-all remote.fetch_custom.fetch \
> +		"+refs/heads/*:refs/remotes/custom-ns/*" &&
> +	git fetch fetch_custom &&
> +	test_commit -C fetch_upstream u_refspec_post &&
> +	git checkout --track=fetch -b local_refspec custom-ns/fetch_refspec &&
> +	test_cmp_rev refs/remotes/custom-ns/fetch_refspec HEAD
> +'
> +
> +test_expect_success 'checkout --track=inherit,direct is rejected' '
> +	test_must_fail git checkout --track=inherit,direct -b bad fetch_upstream/fetch_new 2>err &&
> +	test_grep "cannot combine" err
> +'
> +
> +test_expect_success 'checkout --track=fetch then --track=direct drops fetch (last-one-wins)' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_lastwin &&
> +	test_commit -C fetch_upstream u_lastwin &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin &&
> +	test_must_fail git checkout --track=fetch --track=direct \
> +		-b local_lastwin fetch_upstream/fetch_lastwin &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
> +'
> +
> +test_expect_success 'checkout --track=fetch,inherit fetches and inherits' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_inherit &&
> +	test_commit -C fetch_upstream u_inherit &&
> +	git fetch fetch_upstream fetch_inherit &&
> +	git checkout -b base_inherit fetch_upstream/fetch_inherit &&
> +	test_commit -C fetch_upstream u_inherit2 &&
> +	git checkout main &&
> +	git checkout --track=fetch,inherit -b local_inherit base_inherit &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD &&
> +	test_cmp_config fetch_upstream branch.local_inherit.remote &&
> +	test_cmp_config refs/heads/fetch_inherit branch.local_inherit.merge
> +'
> +
> +test_expect_success 'checkout --track=bogus reports an error' '
> +	git checkout main &&
> +	test_must_fail git checkout --track=bogus -b bogus_branch fetch_upstream/fetch_new 2>err &&
> +	test_grep "expects" err
> +'
> +
> +test_expect_success 'switch --track=fetch -c picks up branch created upstream after clone' '
> +	git checkout main &&
> +	git -C fetch_upstream checkout -b fetch_switch &&
> +	test_commit -C fetch_upstream u_switch &&
> +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_switch &&
> +	git switch --track=fetch -c local_switch fetch_upstream/fetch_switch &&
> +	test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
> +'
> +
>   test_done
> 
> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0


  reply	other threads:[~2026-05-11 13:16 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-24 10:03 [PATCH] checkout: add --fetch to fetch remote before resolving start-point Harald Nordgren via GitGitGadget
2026-04-24 13:48 ` Ramsay Jones
2026-04-24 17:12 ` D. Ben Knoble
2026-04-25 17:24   ` Comments on Phillip's review Harald Nordgren
2026-04-25 17:44     ` Wrong subject line Harald Nordgren
2026-04-24 17:38 ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point Kristoffer Haugsbakk
2026-04-25 17:41   ` Comments on Phillip's review Harald Nordgren
2026-04-25 17:44     ` Wrong subject line Harald Nordgren
2026-04-26  7:07       ` Kristoffer Haugsbakk
2026-04-26 15:15         ` [PATCH] remote: add --set-head option to 'git remote add' Harald Nordgren
2026-04-24 17:42 ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point Marc Branchaud
2026-04-25 17:48   ` Wrong subject line Harald Nordgren
2026-04-24 22:21 ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point Junio C Hamano
2026-04-25  2:54   ` Junio C Hamano
2026-04-25 17:58     ` Multiple remotes Harald Nordgren
2026-04-25 21:57       ` Ben Knoble
2026-04-25 22:54         ` gh Harald Nordgren
2026-04-25 18:12 ` [PATCH v2] checkout: add --fetch to fetch remote before resolving start-point Harald Nordgren via GitGitGadget
2026-04-26  7:24   ` [PATCH v3] " Harald Nordgren via GitGitGadget
2026-04-26 15:54     ` Ramsay Jones
2026-04-26 18:32     ` [PATCH v4] " Harald Nordgren via GitGitGadget
2026-04-28  1:47       ` Junio C Hamano
2026-04-28  8:44         ` [PATCH] " Harald Nordgren
2026-04-28  9:03       ` [PATCH v5] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren via GitGitGadget
2026-05-03 20:59         ` Junio C Hamano
2026-05-03 22:32           ` [PATCH] checkout: add --autostash option for branch switching Harald Nordgren
2026-05-03 22:31         ` [PATCH v6] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren via GitGitGadget
2026-05-07 20:12           ` Harald Nordgren
2026-05-08 13:15             ` Phillip Wood
2026-05-08 22:40               ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point Harald Nordgren
2026-05-08 22:52           ` [PATCH v7] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren via GitGitGadget
2026-05-11 13:16             ` Phillip Wood [this message]
2026-05-11 13:47             ` [PATCH v8] " Harald Nordgren via GitGitGadget
2026-05-12  0:32               ` Junio C Hamano
2026-05-12 10:55               ` [PATCH v9] " Harald Nordgren via GitGitGadget

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=98410b9c-8ed6-41df-b508-ca9e15d4ee53@gmail.com \
    --to=phillip.wood123@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=haraldnordgren@gmail.com \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=marcnarc@gmail.com \
    --cc=phillip.wood@dunelm.org.uk \
    --cc=ramsay@ramsayjones.plus.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox