[PATCH v13 0/2] checkout: --track=fetch
From: Harald Nordgren via GitGitGadget <hidden>
Date: 2026-05-23 19:48:37
* Create a preparatory commit that exposes find_tracking_remote_for_ref() and advise_ambiguous_fetch_refspec() from branch.c, so checkout can reuse the same lookup git branch --track uses. * Use advise_ambiguous_fetch_refspec() for the "multiple remotes match" case, so the wording matches git branch --track. Harald Nordgren (2): branch: expose helpers for finding the remote owning a tracking ref checkout: extend --track with a "fetch" mode to refresh start-point Documentation/git-checkout.adoc | 17 +- Documentation/git-switch.adoc | 5 +- branch.c | 96 ++++++----- branch.h | 16 ++ builtin/checkout.c | 139 +++++++++++++++- t/t7201-co.sh | 276 ++++++++++++++++++++++++++++++++ 6 files changed, 498 insertions(+), 51 deletions(-) base-commit: aec3f587505a472db67e9462d0702e7d463a449d Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v13 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v13 Pull-Request: https://github.com/git/git/pull/2281 Range-diff vs v12: -: ---------- > 1: 2369afad24 branch: expose helpers for finding the remote owning a tracking ref 1: bcd034dbed ! 2: 60adf0e67d checkout: extend --track with a "fetch" mode to refresh start-point @@ Commit message git checkout -b new_branch --track origin/some-branch Identify the remote whose configured fetch refspec maps to - <start-point>, then run "git fetch <remote> <src-ref>" for just that - ref so other remote-tracking branches are left untouched. When - <start-point> is a bare <remote> (e.g. "origin"), follow + <start-point> using find_tracking_remote_for_ref() (the same lookup + "--track" uses to pick which remote to record in + branch.<name>.remote), then run "git fetch <remote> <src-ref>" for + just that ref so other remote-tracking branches are left untouched. + When <start-point> is a bare <remote> (e.g. "origin"), follow refs/remotes/<remote>/HEAD to learn which branch to refresh. If "git fetch" fails but the remote-tracking ref already exists locally, warn and proceed from the existing tip; otherwise abort. @@ builtin/checkout.c: struct branch_info { char *checkout; }; -+struct fetch_target_cb { -+ char *dst; -+ struct string_list matches; -+}; -+ -+static int match_fetch_target(struct remote *remote, void *priv) -+{ -+ struct fetch_target_cb *cb = priv; -+ struct refspec_item q = { .dst = cb->dst }; -+ -+ if (!remote_find_tracking(remote, &q) && q.src) -+ string_list_append(&cb->matches, remote->name)->util = q.src; -+ return 0; -+} -+ +static void fetch_remote_for_start_point(const char *arg, int quiet) +{ + struct strbuf dst = STRBUF_INIT; -+ struct fetch_target_cb cb = { .matches = STRING_LIST_INIT_NODUP }; ++ struct tracking tracking; ++ struct string_list tracking_srcs = STRING_LIST_INIT_DUP; ++ struct string_list ambiguous_remotes = STRING_LIST_INIT_DUP; + struct child_process cmd = CHILD_PROCESS_INIT; + struct object_id oid; + struct remote *named_remote; + int bare_ns; -+ size_t i; + + strbuf_addf(&dst, "refs/remotes/%s", arg); + if (check_refname_format(dst.buf, 0)) @@ builtin/checkout.c: struct branch_info { + free(head_path); + } + -+ cb.dst = dst.buf; -+ for_each_remote(match_fetch_target, &cb); -+ -+ if (cb.matches.nr > 1) { -+ struct strbuf msg = STRBUF_INIT; -+ -+ strbuf_addf(&msg, -+ _("cannot fetch start-point '%s': fetch refspecs " -+ "of multiple remotes map to the same destination:"), -+ arg); -+ for (i = 0; i < cb.matches.nr; i++) -+ strbuf_addf(&msg, "\n %s", cb.matches.items[i].string); -+ strbuf_addstr(&msg, -+ _("\nadjust 'remote.<name>.fetch' so only one " -+ "remote maps there, or omit '=fetch'")); -+ die("%s", msg.buf); ++ memset(&tracking, 0, sizeof(tracking)); ++ tracking.spec.dst = dst.buf; ++ tracking.srcs = &tracking_srcs; ++ find_tracking_remote_for_ref(&tracking, &ambiguous_remotes); ++ ++ if (tracking.matches > 1) { ++ int status = die_message(_("cannot fetch start-point '%s': " ++ "fetch refspecs of multiple remotes " ++ "map to '%s'"), arg, dst.buf); ++ advise_ambiguous_fetch_refspec(dst.buf, &ambiguous_remotes); ++ exit(status); + } + -+ if (!cb.matches.nr) { ++ if (!tracking.matches) { + if (bare_ns && named_remote && + remote_is_configured(named_remote, 1)) + die(_("cannot fetch start-point '%s': " @@ builtin/checkout.c: struct branch_info { + strvec_push(&cmd.args, "fetch"); + if (quiet) + strvec_push(&cmd.args, "--quiet"); -+ strvec_pushl(&cmd.args, cb.matches.items[0].string, -+ (char *)cb.matches.items[0].util, NULL); ++ strvec_pushl(&cmd.args, tracking.remote, ++ tracking_srcs.items[0].string, NULL); + cmd.git_cmd = 1; + if (run_command(&cmd)) { + if (!refs_read_ref(get_main_ref_store(the_repository), @@ builtin/checkout.c: struct branch_info { + die(_("failed to fetch start-point '%s'"), arg); + } + -+ for (i = 0; i < cb.matches.nr; i++) -+ free(cb.matches.items[i].util); -+ string_list_clear(&cb.matches, 0); ++ string_list_clear(&tracking_srcs, 0); ++ string_list_clear(&ambiguous_remotes, 0); + strbuf_release(&dst); +} + @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh + test_must_fail git checkout --track=fetch -b local_ambig ambig_ns/fetch_ambig 2>err && + test_grep "fetch_ambig_a" err && + test_grep "fetch_ambig_b" err && -+ test_grep "remote.<name>.fetch" err && ++ test_grep "tracking namespaces" err && + test_must_fail git rev-parse --verify refs/heads/local_ambig +' + -- gitgitgadget