All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Harald Nordgren via GitGitGadget" <gitgitgadget@gmail.com>
To: 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>,
	Phillip Wood <phillip.wood123@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v15 0/2] checkout: --track=fetch
Date: Wed, 24 Jun 2026 21:54:56 +0000	[thread overview]
Message-ID: <pull.2281.v15.git.git.1782338098.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2281.v14.git.git.1781786652.gitgitgadget@gmail.com>

Extend checkout --track with a fetch mode to refresh start-point.

Changes in v15:

 * Reword commit message to lead with motivation.
 * Drop RESOLVE_REF_NO_RECURSE so <ns>/HEAD lookup matches what git checkout
   does for the same ref. Drop redundant check_refname_format on a ref we
   just read. Replace memset with brace initializer. Use refs_ref_exists and
   pass NULL for the OID out-params.
 * Split the "set HEAD" error onto its own line via die_message and advise,
   matching the suggested format.
 * Remove 6 redundant tests, replace test $a = $b with test_cmp_rev, and
   rename test titles to avoid "namespace".

Changes in v14:

 * Handle .h files in a better way.

Changes in v13:

 * 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              | 138 +++++++++++++++++++-
 t/t7201-co.sh                   | 222 ++++++++++++++++++++++++++++++++
 6 files changed, 443 insertions(+), 51 deletions(-)


base-commit: ab776a62a78576513ee121424adb19597fbb7613
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v15
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v15
Pull-Request: https://github.com/git/git/pull/2281

Range-diff vs v14:

 1:  f79689c23d = 1:  8139490c36 branch: expose helpers for finding the remote owning a tracking ref
 2:  8518f090b1 ! 2:  b7e387f123 checkout: extend --track with a "fetch" mode to refresh start-point
     @@ Metadata
       ## Commit message ##
          checkout: extend --track with a "fetch" mode to refresh start-point
      
     -    Add a "fetch" mode to the "--track" option of "git checkout" / "git
     -    switch" that refreshes <start-point> before checking it out:
     +    Forking from an existing remote branch without refreshing first often
     +    has consequences: you start work that has already been done, or you
     +    build on an old version of the code which causes big conflicts later
     +    when you pull. The workaround is two commands ("git fetch <remote>
     +    <branch> && git checkout -b <topic> <remote>/<branch>"), and when
     +    the fetch is skipped the checkout silently starts from a stale tip.
      
     -        git checkout -b new_branch --track=fetch origin/some-branch
     +    Users may already expect "<remote>/<branch>" to refer to the latest
     +    tip on the remote. While this blurs the line between fetch and
     +    checkout, git already does this in places where it pays off: "git
     +    clone" fetches and checks out, and "git pull" fetches and merges.
      
     -    is shorthand for
     +    Add a "fetch" mode to "--track" that refreshes <start-point> before
     +    checking it out:
      
     -        git fetch origin some-branch
     -        git checkout -b new_branch --track origin/some-branch
     +        git checkout -b new_branch --track=fetch origin/some-branch
      
     -    Identify the remote whose configured fetch refspec maps to
     -    <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.
     +    Only the requested branch is fetched 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.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     @@ builtin/checkout.c: struct branch_info {
      +static void fetch_remote_for_start_point(const char *arg, int quiet)
      +{
      +	struct strbuf dst = STRBUF_INIT;
     -+	struct tracking tracking;
     ++	struct tracking tracking = { 0 };
      +	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;
      +
     @@ builtin/checkout.c: struct branch_info {
      +		const char *head_target =
      +			refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
      +						head_path,
     -+						RESOLVE_REF_READING |
     -+						RESOLVE_REF_NO_RECURSE,
     -+						&oid, NULL);
     ++						RESOLVE_REF_READING,
     ++						NULL, NULL);
      +		if (head_target &&
      +		    starts_with(head_target, dst.buf) &&
     -+		    head_target[dst.len] == '/' &&
     -+		    !check_refname_format(head_target, 0)) {
     ++		    head_target[dst.len] == '/') {
      +			strbuf_reset(&dst);
      +			strbuf_addstr(&dst, head_target);
      +			bare_ns = 0;
     @@ builtin/checkout.c: struct branch_info {
      +		free(head_path);
      +	}
      +
     -+	memset(&tracking, 0, sizeof(tracking));
      +	tracking.spec.dst = dst.buf;
      +	tracking.srcs = &tracking_srcs;
      +	find_tracking_remote_for_ref(&tracking, &ambiguous_remotes);
     @@ builtin/checkout.c: struct branch_info {
      +
      +	if (!tracking.matches) {
      +		if (bare_ns && named_remote &&
     -+		    remote_is_configured(named_remote, 1))
     -+			die(_("cannot fetch start-point '%s': "
     -+			      "'refs/remotes/%s/HEAD' is not set; run "
     -+			      "'git remote set-head %s --auto' to set it"),
     -+			    arg, arg, arg);
     ++		    remote_is_configured(named_remote, 1)) {
     ++			int status = die_message(_("cannot fetch start-point '%s' "
     ++						   "because 'refs/remotes/%s/HEAD' "
     ++						   "does not exist."), arg, arg);
     ++			advise(_("To create it run\n"
     ++				 "\n"
     ++				 "    git remote set-head %s --auto\n"), arg);
     ++			exit(status);
     ++		}
      +		die(_("cannot fetch start-point '%s': no configured remote's "
      +		      "fetch refspec matches it"), arg);
      +	}
     @@ builtin/checkout.c: struct branch_info {
      +		     tracking_srcs.items[0].string, NULL);
      +	cmd.git_cmd = 1;
      +	if (run_command(&cmd)) {
     -+		if (!refs_read_ref(get_main_ref_store(the_repository),
     -+				   dst.buf, &oid))
     ++		if (refs_ref_exists(get_main_ref_store(the_repository), dst.buf))
      +			warning(_("failed to fetch start-point '%s'; "
      +				  "using existing '%s'"), arg, dst.buf);
      +		else
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	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 update-ref refs/heads/snapshot_other 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_cmp_rev refs/remotes/fetch_upstream/fetch_other snapshot_other
      +'
      +
      +test_expect_success 'checkout --track=fetch with bare remote name fetches only <remote>/HEAD target' '
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	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 update-ref refs/heads/snapshot_unrelated \
     ++		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_cmp_rev refs/remotes/fetch_upstream/fetch_unrelated snapshot_unrelated
      +'
      +
      +test_expect_success 'checkout --track=fetch aborts and does not create branch when no existing ref' '
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_cmp_rev refs/remotes/custom-ns/fetch_refspec HEAD
      +'
      +
     -+test_expect_success 'checkout --track=fetch on namespace bare name follows <ns>/HEAD' '
     ++test_expect_success 'checkout --track=fetch on bare remote-tracking prefix follows <prefix>/HEAD' '
      +	git checkout main &&
      +	git remote add fetch_ns ./fetch_upstream &&
      +	test_when_finished "git remote remove fetch_ns" &&
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_cmp_config refs/heads/main branch.local_ns.merge
      +'
      +
     -+test_expect_success '--track=fetch on bare hierarchical remote name follows <ns>/HEAD' '
     -+	git checkout main &&
     -+	git remote add nested/bare ./fetch_upstream &&
     -+	test_when_finished "git remote remove nested/bare" &&
     -+	test_when_finished "git update-ref -d refs/remotes/nested/bare/HEAD" &&
     -+	git fetch nested/bare &&
     -+	git symbolic-ref refs/remotes/nested/bare/HEAD \
     -+		refs/remotes/nested/bare/main &&
     -+	git -C fetch_upstream checkout main &&
     -+	test_commit -C fetch_upstream u_nested_bare_post &&
     -+	git checkout --track=fetch -b local_nested_bare nested/bare &&
     -+	test_cmp_rev refs/remotes/nested/bare/main HEAD
     -+'
     -+
     -+test_expect_success 'checkout --track=fetch handles hierarchical remote name' '
     -+	git checkout main &&
     -+	git remote add nested/remote ./fetch_upstream &&
     -+	test_when_finished "git remote remove nested/remote" &&
     -+	git -C fetch_upstream checkout -b fetch_hier &&
     -+	test_commit -C fetch_upstream u_hier &&
     -+	test_must_fail git rev-parse --verify refs/remotes/nested/remote/fetch_hier &&
     -+	git checkout --track=fetch -b local_hier nested/remote/fetch_hier &&
     -+	test_cmp_rev refs/remotes/nested/remote/fetch_hier HEAD
     -+'
     -+
      +test_expect_success 'checkout --track=fetch dies on bare remote name with no <ns>/HEAD' '
      +	git checkout main &&
      +	git remote add fetch_nohead ./fetch_upstream &&
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_must_fail git rev-parse --verify refs/heads/local_unknown
      +'
      +
     -+test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside namespace' '
     ++test_expect_success 'checkout --track=fetch rejects <ns>/HEAD pointing outside the tracking prefix' '
      +	git checkout main &&
      +	git remote add fetch_crossns ./fetch_upstream &&
      +	test_when_finished "git remote remove fetch_crossns" &&
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_must_fail git rev-parse --verify refs/heads/local_invalid
      +'
      +
     -+test_expect_success 'checkout --track=fetch,inherit rejects invalid refname components' '
     -+	git checkout main &&
     -+	test_must_fail git checkout --track=fetch,inherit -b local_invalid \
     -+		"foo..bar" 2>err &&
     -+	test_grep "valid" err &&
     -+	test_must_fail git rev-parse --verify refs/heads/local_invalid
     -+'
     -+
      +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=direct,inherit is rejected' '
     -+	test_must_fail git checkout --track=direct,inherit -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 &&
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_lastwin
      +'
      +
     -+test_expect_success 'checkout --track=fetch then --no-track drops fetch' '
     -+	git checkout main &&
     -+	git -C fetch_upstream checkout -b fetch_notrack &&
     -+	test_commit -C fetch_upstream u_notrack &&
     -+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack &&
     -+	test_must_fail git checkout --track=fetch --no-track \
     -+		-b local_notrack fetch_upstream/fetch_notrack &&
     -+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_notrack
     -+'
     -+
      +test_expect_success 'checkout --track=fetch,inherit fetches remote-tracking start-point' '
      +	git checkout main &&
      +	git -C fetch_upstream checkout -b fetch_inherit &&
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_cmp_rev refs/remotes/fetch_upstream/fetch_inherit HEAD
      +'
      +
     -+test_expect_success 'checkout --track=fetch,inherit errors when start-point does not map to a remote' '
     -+	git checkout main &&
     -+	test_must_fail git checkout --track=fetch,inherit -b bad main 2>err &&
     -+	test_grep "no configured remote" err &&
     -+	test_must_fail git rev-parse --verify refs/heads/bad
     -+'
     -+
      +test_expect_success 'checkout --track=fetch on local start-point errors' '
      +	git checkout main &&
      +	test_must_fail git checkout --track=fetch -b bad main 2>err &&

-- 
gitgitgadget

  parent reply	other threads:[~2026-06-24 21:55 UTC|newest]

Thread overview: 70+ 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-06-18 12:36   ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point Harald Nordgren
2026-06-18 17:47     ` D. Ben Knoble
2026-04-24 17:38 ` 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-06-18 12:38               ` [PATCH v6] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren
2026-05-08 22:52           ` [PATCH v7] " Harald Nordgren via GitGitGadget
2026-05-11 13:16             ` Phillip Wood
2026-05-11 13:47             ` [PATCH v8] " Harald Nordgren via GitGitGadget
2026-05-12  0:32               ` Junio C Hamano
2026-05-18  8:06                 ` Harald Nordgren
2026-05-12 10:55               ` [PATCH v9] " Harald Nordgren via GitGitGadget
2026-05-18  8:04                 ` [PATCH v10] " Harald Nordgren via GitGitGadget
2026-05-19  6:16                   ` Junio C Hamano
2026-05-19  7:52                     ` Harald Nordgren
2026-05-19  8:16                       ` Junio C Hamano
2026-05-19  8:32                         ` Harald Nordgren
2026-05-19  8:38                           ` Harald Nordgren
2026-05-19  7:58                   ` [PATCH v11] " Harald Nordgren via GitGitGadget
2026-05-19 10:34                     ` Junio C Hamano
2026-05-21  9:49                       ` Phillip Wood
2026-05-21 10:24                         ` Harald Nordgren
2026-05-21 12:58                         ` Junio C Hamano
2026-05-21 14:06                           ` Phillip Wood
2026-05-21 23:53                             ` Junio C Hamano
2026-06-13 17:38                             ` Harald Nordgren
2026-05-21 10:20                     ` [PATCH v12] " Harald Nordgren via GitGitGadget
2026-05-23 19:48                       ` [PATCH v13 0/2] checkout: --track=fetch Harald Nordgren via GitGitGadget
2026-05-23 19:48                         ` [PATCH v13 1/2] branch: expose helpers for finding the remote owning a tracking ref Harald Nordgren via GitGitGadget
2026-05-23 19:48                         ` [PATCH v13 2/2] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren via GitGitGadget
2026-06-17 19:15                         ` [PATCH v13 0/2] checkout: --track=fetch Junio C Hamano
2026-06-17 22:10                           ` Harald Nordgren
2026-06-18  2:50                             ` Junio C Hamano
2026-06-18 12:44                         ` [PATCH v14 " Harald Nordgren via GitGitGadget
2026-06-18 12:44                           ` [PATCH v14 1/2] branch: expose helpers for finding the remote owning a tracking ref Harald Nordgren via GitGitGadget
2026-06-18 12:44                           ` [PATCH v14 2/2] checkout: extend --track with a "fetch" mode to refresh start-point Harald Nordgren via GitGitGadget
2026-06-23 13:49                             ` Phillip Wood
2026-06-23 17:47                               ` Harald Nordgren
2026-06-24 23:20                                 ` Junio C Hamano
2026-06-25  1:17                                   ` Ben Knoble
2026-06-24 21:54                           ` Harald Nordgren via GitGitGadget [this message]
2026-06-24 21:54                             ` [PATCH v15 1/2] branch: expose helpers for finding the remote owning a tracking ref Harald Nordgren via GitGitGadget
2026-06-24 21:54                             ` [PATCH v15 2/2] checkout: extend --track with a "fetch" mode to refresh start-point 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=pull.2281.v15.git.git.1782338098.gitgitgadget@gmail.com \
    --to=gitgitgadget@gmail.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=haraldnordgren@gmail.com \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=marcnarc@gmail.com \
    --cc=phillip.wood123@gmail.com \
    --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.