git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v6] checkout: extend --track with a "fetch" mode to refresh start-point

From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
Date
May 3, 2026, 22:31 UTC
Message-ID
<pull.2281.v6.git.git.1777847487823.gitgitgadget@gmail.com>
In-Reply-To
<pull.2281.v5.git.git.1777367012441.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>

If you want to fork your topic branch from the very latest of the tip of a branch your remote has, you would do:

    git fetch origin some-branch
    git checkout -b new_branch --track origin/some-branch

Extend the "--track" option of "git checkout" and allow users to write

    git checkout -b new_branch --track=fetch origin/some-branch

to (1) fetch 'some-branch' from the remote 'origin', updating the remote-tracking branch 'origin/some-branch', (2) arrange subsequent 'git pull' on 'new_branch' to interact with 'origin/some-branch' and (3) fork 'new_branch' from it.

In the value of the '--track' option, 'fetch' can be combined with the existing 'direct' (default) and 'inherit' modes via a comma-separated list. Examples:

    git checkout -b new_branch --track=fetch,inherit some_local_branch
    git switch -c new_branch --track=fetch origin/some-branch

When "fetch" is requested and <start-point> is in <remote>/<branch> form, run "git fetch <remote> <branch>" before resolving the ref, so that other remote-tracking branches are left untouched. If <start-point> is a bare remote name like "origin" (which resolves to that remote's default branch), "git fetch <remote>" is run instead, since the target branch is not known up front. Abort the checkout if the fetch fails.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    checkout: add --fetch to fetch remote before resolving start-point
    
    Commit message only, no code/doc/test changes. Restructured the opening
    around the user-visible workflow before introducing '--track=fetch',
    reordered all example invocations to ' -b/-c --track[=...] ', dropped
    the "wrong/stale start-point" and "arbitrary commit" framings, and
    trimmed the over-explanation of the narrowed fetch.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v6
Pull-Request: https://github.com/git/git/pull/2281
Range-diff vs v5:
 1:  8ebc2f94b9 ! 1:  1b42c648b9 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
      
     -    A common workflow is:
     +    If you want to fork your topic branch from the very latest of the
     +    tip of a branch your remote has, you would do:
      
     -        git fetch origin
     +        git fetch origin some-branch
              git checkout -b new_branch --track origin/some-branch
      
     -    The first command exists so the second sees an up-to-date view of the
     -    remote. If it is forgotten, origin/some-branch points at a stale
     -    commit and the new local branch is created from the wrong start
     -    point. This only matters when the user is setting up tracking and
     -    expects the new branch to start at the freshest tip; for a one-off
     -    checkout of an arbitrary commit there is no reason to "freshen" the
     -    start-point.
     +    Extend the "--track" option of "git checkout" and allow users to
     +    write
      
     -    Tie the new behavior to --track for that reason: extend its argument
     -    to take a comma-separated list, where "fetch" can be combined with the
     -    existing "direct" (default) and "inherit" modes. Examples:
     +        git checkout -b new_branch --track=fetch origin/some-branch
      
     -        git checkout --track=fetch -b new_branch origin/some-branch
     -        git checkout --track=fetch,inherit -b new_branch some_local_branch
     -        git switch --track=fetch -c new_branch origin/some-branch
     +    to (1) fetch 'some-branch' from the remote 'origin', updating the
     +    remote-tracking branch 'origin/some-branch', (2) arrange subsequent
     +    'git pull' on 'new_branch' to interact with 'origin/some-branch' and
     +    (3) fork 'new_branch' from it.
     +
     +    In the value of the '--track' option, 'fetch' can be combined with
     +    the existing 'direct' (default) and 'inherit' modes via a
     +    comma-separated list. Examples:
     +
     +        git checkout -b new_branch --track=fetch,inherit some_local_branch
     +        git switch -c new_branch --track=fetch origin/some-branch
      
          When "fetch" is requested and <start-point> is in <remote>/<branch>
     -    form, run "git fetch <remote> <branch>" before resolving the ref. This
     -    narrows the fetch to the requested branch so that other
     -    remote-tracking branches are left untouched -- many tools rely on the
     -    stability of remote-tracking refs between explicit fetches. If
     +    form, run "git fetch <remote> <branch>" before resolving the ref, so
     +    that other remote-tracking branches are left untouched. If
          <start-point> is a bare remote name like "origin" (which resolves to
          that remote's default branch), "git fetch <remote>" is run instead,
          since the target branch is not known up front. Abort the checkout if
 Documentation/git-checkout.adoc | 10 +++-
 Documentation/git-switch.adoc   | 10 +++-
 builtin/checkout.c              | 85 +++++++++++++++++++++++++++++++--
 t/t7201-co.sh                   | 81 +++++++++++++++++++++++++++++++
 4 files changed, 179 insertions(+), 7 deletions(-)
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index 43ccf47cf6..3b8292612d 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -158,11 +158,19 @@ of it").
 	resets _<branch>_ to the start point instead of failing.
 
 `-t`::
-`--track[=(direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
 	When creating a new branch, set up "upstream" configuration. See
 	`--track` in linkgit:git-branch[1] for details. As a convenience,
 	--track without -b implies branch creation.
 +
+The argument is a comma-separated list. `direct` (the default) and
+`inherit` select the tracking mode. Adding `fetch` requests that the
+remote be fetched before _<start-point>_ is resolved, so the new branch
+starts from a fresh tip: when _<start-point>_ is in
+_<remote>/<branch>_ form, only that branch is updated; when it is a
+bare remote name (e.g. `origin`), the whole remote is fetched. If the
+fetch fails, the checkout is aborted.
++
 If no `-b` option is given, the name of the new branch will be
 derived from the remote-tracking branch, by looking at the local part of
 the refspec configured for the corresponding remote, and then stripping
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc
index 87707e9265..35a03e8a52 100644
--- a/Documentation/git-switch.adoc
+++ b/Documentation/git-switch.adoc
@@ -154,11 +154,19 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 `-t`::
-`--track[ (direct|inherit)]`::
+`--track[=(direct|inherit|fetch)[,...]]`::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
 +
+The argument is a comma-separated list. `direct` (the default) and
+`inherit` select the tracking mode. Adding `fetch` requests that the
+remote be fetched before _<start-point>_ is resolved, so the new branch
+starts from a fresh tip: when _<start-point>_ is in
+_<remote>/<branch>_ form, only that branch is updated; when it is a
+bare remote name (e.g. `origin`), the whole remote is fetched. If the
+fetch fails, the switch is aborted.
++
 If no `-c` option is given, the name of the new branch will be derived
 from the remote-tracking branch, by looking at the local part of the
 refspec configured for the corresponding remote, and then stripping
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e61886..de4d7c00c7 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -30,7 +30,9 @@
 #include "repo-settings.h"
 #include "resolve-undo.h"
 #include "revision.h"
+#include "run-command.h"
 #include "setup.h"
+#include "strvec.h"
 #include "submodule.h"
 #include "symlinks.h"
 #include "trace2.h"
@@ -61,6 +63,7 @@ struct checkout_opts {
 	int count_checkout_paths;
 	int overlay_mode;
 	int dwim_new_local_branch;
+	int fetch;
 	int discard_changes;
 	int accept_ref;
 	int accept_pathspec;
@@ -112,6 +115,74 @@ struct branch_info {
 	char *checkout;
 };
 
+static void fetch_remote_for_start_point(const char *arg)
+{
+	const char *slash;
+	char *remote_name;
+	struct remote *remote;
+	struct child_process cmd = CHILD_PROCESS_INIT;
+
+	if (!arg || !*arg)
+		return;
+
+	slash = strchr(arg, '/');
+	if (slash == arg)
+		return;
+	remote_name = slash ? xstrndup(arg, slash - arg) : xstrdup(arg);
+
+	remote = remote_get(remote_name);
+	if (!remote || !remote_is_configured(remote, 1)) {
+		free(remote_name);
+		return;
+	}
+
+	strvec_pushl(&cmd.args, "fetch", remote_name, NULL);
+	if (slash && slash[1])
+		strvec_push(&cmd.args, slash + 1);
+	cmd.git_cmd = 1;
+	free(remote_name);
+	if (run_command(&cmd))
+		die(_("failed to fetch start-point '%s'"), arg);
+}
+
+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 ret = 0;
+
+	if (unset) {
+		opts->track = BRANCH_TRACK_NEVER;
+		opts->fetch = 0;
+		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")) {
+			opts->track = BRANCH_TRACK_EXPLICIT;
+		} else if (!strcmp(item->string, "inherit")) {
+			opts->track = BRANCH_TRACK_INHERIT;
+		} else {
+			ret = error(_("option `%s' expects \"%s\", \"%s\", "
+				      "or \"%s\""),
+				    "--track", "direct", "inherit", "fetch");
+			break;
+		}
+	}
+
+	string_list_clear(&tokens, 0);
+	return ret;
+}
+
 static void branch_info_release(struct branch_info *info)
 {
 	free(info->name);
@@ -1237,7 +1308,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 +1804,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 +2012,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..39236dca12 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -801,4 +801,85 @@ 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 the remote' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_new2 &&
+	test_commit -C fetch_upstream u_new2 &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2 &&
+	git checkout --track=fetch -b local_from_remote fetch_upstream &&
+	git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2
+'
+
+test_expect_success 'checkout --track=fetch aborts and does not create branch on fetch failure' '
+	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,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
-- 
gitgitgadget
Previous: Harald NordgrenNext: Harald Nordgren
Message 29 of 73 in “checkout: add --fetch to fetch remote before resolving start-point”
  1. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 24, 2026
  2. Ramsay JonesApr 24, 2026
  3. D. Ben KnobleApr 24, 2026
  4. Comments on Phillip's reviewHarald Nordgren, Apr 25, 2026
  5. Wrong subject lineHarald Nordgren, Apr 25, 2026
  6. Harald NordgrenJun 18, 2026
  7. D. Ben KnobleJun 18, 2026
  8. Kristoffer HaugsbakkApr 24, 2026
  9. Comments on Phillip's reviewHarald Nordgren, Apr 25, 2026
  10. Wrong subject lineHarald Nordgren, Apr 25, 2026
  11. Kristoffer HaugsbakkApr 26, 2026
  12. Harald NordgrenApr 26, 2026
  13. Marc BranchaudApr 24, 2026
  14. Wrong subject lineHarald Nordgren, Apr 25, 2026
  15. Junio C HamanoApr 24, 2026
  16. Junio C HamanoApr 25, 2026
  17. Multiple remotesHarald Nordgren, Apr 25, 2026
  18. Ben KnobleApr 25, 2026
  19. ghHarald Nordgren, Apr 25, 2026
  20. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 25, 2026
  21. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 26, 2026
  22. Ramsay JonesApr 26, 2026
  23. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 26, 2026
  24. Junio C HamanoApr 28, 2026
  25. Harald NordgrenApr 28, 2026
  26. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Apr 28, 2026
  27. Junio C HamanoMay 3, 2026
  28. checkout: add --autostash option for branch switchingHarald Nordgren, May 3, 2026
  29. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 3, 2026
  30. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren, May 7, 2026
  31. Phillip WoodMay 8, 2026
  32. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren, May 8, 2026
  33. Harald NordgrenJun 18, 2026
  34. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 8, 2026
  35. Phillip WoodMay 11, 2026
  36. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 11, 2026
  37. Junio C HamanoMay 12, 2026
  38. Harald NordgrenMay 18, 2026
  39. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 12, 2026
  40. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 18, 2026
  41. Junio C HamanoMay 19, 2026
  42. Harald NordgrenMay 19, 2026
  43. Junio C HamanoMay 19, 2026
  44. Harald NordgrenMay 19, 2026
  45. Harald NordgrenMay 19, 2026
  46. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 19, 2026
  47. Junio C HamanoMay 19, 2026
  48. Phillip WoodMay 21, 2026
  49. Harald NordgrenMay 21, 2026
  50. Junio C HamanoMay 21, 2026
  51. Phillip WoodMay 21, 2026
  52. Junio C HamanoMay 21, 2026
  53. Harald NordgrenJun 13, 2026
  54. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 21, 2026
  55. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, May 23, 2026
  56. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, May 23, 2026
  57. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 23, 2026
  58. Junio C HamanoJun 17, 2026
  59. Harald NordgrenJun 17, 2026
  60. Junio C HamanoJun 18, 2026
  61. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, Jun 18, 2026
  62. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, Jun 18, 2026
  63. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Jun 18, 2026
  64. Phillip WoodJun 23, 2026
  65. Harald NordgrenJun 23, 2026
  66. Junio C HamanoJun 24, 2026
  67. Ben KnobleJun 25, 2026
  68. Harald NordgrenJun 25, 2026
  69. Kristofer KarlssonJun 25, 2026
  70. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, Jun 24, 2026
  71. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, Jun 24, 2026
  72. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Jun 24, 2026
  73. Junio C HamanoJul 23, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.