* 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
+'
+