git/list[1] front-page[2] threads[3] people[4] search[5] about
wed 2026-10-07 16:48 UTC

[PATCH] checkout: add --fetch to fetch remote before resolving start-point

From
Harald Nordgren via GitGitGadget <gitgitgadget@gmail.com>
Date
Apr 24, 2026, 10:03 UTC
Message-ID
<pull.2281.git.git.1777024991531.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>

Add a --fetch option to git checkout and git switch, plus a checkout.autoFetch config to enable it by default. When set and the start-point argument names a configured remote (either bare, like "origin", or prefixed, like "origin/foo"), fetch that remote before resolving the ref. Aborts the checkout if the fetch fails.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    checkout: add --fetch to fetch remote before resolving start-point
    
    A workflow I run several times a day looks like:
    
    git fetch origin
    git checkout -b new_branch origin/some-branch
    
    
    The first command exists purely to make the second one see an up-to-date
    view of the remote. If I forget it, origin/some-branch points at a stale
    commit, and I end up creating a local branch from the wrong starting
    point.
    
    This series teaches git checkout (and git switch) a new --fetch flag
    that folds the two steps into one:
    
    git checkout --fetch -b new_branch origin/some-branch
    
    
    When the start-point argument names a configured remote — either bare
    (origin, which resolves to the remote's default branch) or in / form —
    git fetch is run before the start-point is resolved. If the fetch fails,
    the checkout aborts and no local branch is created.
    
    A new checkout.autoFetch config option enables the same behavior by
    default, for users who always want it.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2281%2FHaraldNordgren%2Fcheckout-fetch-start-point-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2281/HaraldNordgren/checkout-fetch-start-point-v1
Pull-Request: https://github.com/git/git/pull/2281
 builtin/checkout.c    | 48 ++++++++++++++++++++++++++++++++++++++--
 t/t7201-co.sh         | 51 +++++++++++++++++++++++++++++++++++++++++++
 t/t9902-completion.sh |  1 +
 3 files changed, 98 insertions(+), 2 deletions(-)
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e61886..c8fbc4923b 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 auto_fetch;
 	int discard_changes;
 	int accept_ref;
 	int accept_pathspec;
@@ -112,6 +115,34 @@ 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);
+	cmd.git_cmd = 1;
+	free(remote_name);
+	if (run_command(&cmd))
+		die(_("failed to fetch start-point '%s'"), arg);
+}
+
 static void branch_info_release(struct branch_info *info)
 {
 	free(info->name);
@@ -1237,6 +1268,10 @@ static int git_checkout_config(const char *var, const char *value,
 		opts->dwim_new_local_branch = git_config_bool(var, value);
 		return 0;
 	}
+	if (!strcmp(var, "checkout.autofetch")) {
+		opts->auto_fetch = git_config_bool(var, value);
+		return 0;
+	}
 
 	if (starts_with(var, "submodule."))
 		return git_default_submodule_config(var, value, NULL);
@@ -1942,8 +1977,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->auto_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) {
@@ -2052,6 +2092,8 @@ int cmd_checkout(int argc,
 		OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode (default)")),
 		OPT_BOOL(0, "auto-advance", &opts.auto_advance,
 			 N_("auto advance to the next file when selecting hunks interactively")),
+		OPT_BOOL(0, "fetch", &opts.auto_fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking ref")),
 		OPT_END()
 	};
 
@@ -2102,6 +2144,8 @@ int cmd_switch(int argc,
 			 N_("second guess 'git switch <no-such-branch>'")),
 		OPT_BOOL(0, "discard-changes", &opts.discard_changes,
 			 N_("throw away local modifications")),
+		OPT_BOOL(0, "fetch", &opts.auto_fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking ref")),
 		OPT_END()
 	};
 
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 9bcf7c0b40..60ddebd9c3 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -801,4 +801,55 @@ test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
 	test_cmp_config "" --default "" branch.main2.merge
 '
 
+test_expect_success 'setup upstream for --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 --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 --fetch -b local_new fetch_upstream/fetch_new &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_new HEAD
+'
+
+test_expect_success 'checkout --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 --fetch -b local_from_remote fetch_upstream &&
+	git rev-parse --verify refs/remotes/fetch_upstream/fetch_new2
+'
+
+test_expect_success 'checkout --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 --fetch -b bogus fetch_upstream/does_not_exist &&
+	test_must_fail git rev-parse --verify refs/heads/bogus
+'
+
+test_expect_success 'checkout.autoFetch=true enables fetching without --fetch' '
+	git checkout main &&
+	git -C fetch_upstream checkout -b fetch_cfg &&
+	test_commit -C fetch_upstream u_cfg &&
+	test_must_fail git rev-parse --verify refs/remotes/fetch_upstream/fetch_cfg &&
+	git -c checkout.autoFetch=true checkout -b local_cfg fetch_upstream/fetch_cfg &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_cfg HEAD
+'
+
+test_expect_success 'switch --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 --fetch -c local_switch fetch_upstream/fetch_switch &&
+	test_cmp_rev refs/remotes/fetch_upstream/fetch_switch HEAD
+'
+
 test_done
diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
index 2f9a597ec7..dc1d63669f 100755
--- a/t/t9902-completion.sh
+++ b/t/t9902-completion.sh
@@ -2602,6 +2602,7 @@ test_expect_success 'double dash "git checkout"' '
 	--ignore-other-worktrees Z
 	--recurse-submodules Z
 	--auto-advance Z
+	--fetch Z
 	--progress Z
 	--guess Z
 	--no-guess Z

base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
-- 
gitgitgadget
Next: Ramsay Jones
Message 1 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. Kristoffer HaugsbakkApr 24, 2026
  5. Marc BranchaudApr 24, 2026
  6. Junio C HamanoApr 24, 2026
  7. Junio C HamanoApr 25, 2026
  8. Comments on Phillip's reviewHarald Nordgren, Apr 25, 2026
  9. Comments on Phillip's reviewHarald Nordgren, Apr 25, 2026
  10. Wrong subject lineHarald Nordgren, Apr 25, 2026
  11. Wrong subject lineHarald Nordgren, Apr 25, 2026
  12. Wrong subject lineHarald Nordgren, Apr 25, 2026
  13. Multiple remotesHarald Nordgren, Apr 25, 2026
  14. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 25, 2026
  15. Ben KnobleApr 25, 2026
  16. ghHarald Nordgren, Apr 25, 2026
  17. Kristoffer HaugsbakkApr 26, 2026
  18. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 26, 2026
  19. Harald NordgrenApr 26, 2026
  20. Ramsay JonesApr 26, 2026
  21. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren via GitGitGadget, Apr 26, 2026
  22. Junio C HamanoApr 28, 2026
  23. Harald NordgrenApr 28, 2026
  24. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Apr 28, 2026
  25. Junio C HamanoMay 3, 2026
  26. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 3, 2026
  27. checkout: add --autostash option for branch switchingHarald Nordgren, May 3, 2026
  28. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren, May 7, 2026
  29. Phillip WoodMay 8, 2026
  30. checkout: add --fetch to fetch remote before resolving start-pointHarald Nordgren, May 8, 2026
  31. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 8, 2026
  32. Phillip WoodMay 11, 2026
  33. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 11, 2026
  34. Junio C HamanoMay 12, 2026
  35. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 12, 2026
  36. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 18, 2026
  37. Harald NordgrenMay 18, 2026
  38. Junio C HamanoMay 19, 2026
  39. Harald NordgrenMay 19, 2026
  40. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 19, 2026
  41. Junio C HamanoMay 19, 2026
  42. Harald NordgrenMay 19, 2026
  43. Harald NordgrenMay 19, 2026
  44. Junio C HamanoMay 19, 2026
  45. Phillip WoodMay 21, 2026
  46. checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 21, 2026
  47. Harald NordgrenMay 21, 2026
  48. Junio C HamanoMay 21, 2026
  49. Phillip WoodMay 21, 2026
  50. Junio C HamanoMay 21, 2026
  51. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, May 23, 2026
  52. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, May 23, 2026
  53. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, May 23, 2026
  54. Harald NordgrenJun 13, 2026
  55. Junio C HamanoJun 17, 2026
  56. Harald NordgrenJun 17, 2026
  57. Junio C HamanoJun 18, 2026
  58. Harald NordgrenJun 18, 2026
  59. Harald NordgrenJun 18, 2026
  60. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, Jun 18, 2026
  61. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, Jun 18, 2026
  62. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Jun 18, 2026
  63. D. Ben KnobleJun 18, 2026
  64. Phillip WoodJun 23, 2026
  65. Harald NordgrenJun 23, 2026
  66. 0/2 checkout: --track=fetchHarald Nordgren via GitGitGadget, Jun 24, 2026
  67. 1/2 branch: expose helpers for finding the remote owning a tracking refHarald Nordgren via GitGitGadget, Jun 24, 2026
  68. 2/2 checkout: extend --track with a "fetch" mode to refresh start-pointHarald Nordgren via GitGitGadget, Jun 24, 2026
  69. Junio C HamanoJun 24, 2026
  70. Ben KnobleJun 25, 2026
  71. Harald NordgrenJun 25, 2026
  72. Kristofer KarlssonJun 25, 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.