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
next prev parent 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.