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>,
	Harald Nordgren <haraldnordgren@gmail.com>,
	Harald Nordgren <haraldnordgren@gmail.com>
Subject: [PATCH v2] checkout: add --fetch to fetch remote before resolving start-point
Date: Sat, 25 Apr 2026 18:12:35 +0000	[thread overview]
Message-ID: <pull.2281.v2.git.git.1777140755373.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2281.git.git.1777024991531.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

A common workflow is:

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

The first command exists purely 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.

Teach checkout (and switch) a --fetch flag that folds the two steps
into one:

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

When --fetch is given and <start-point> names a configured remote
(either bare, like "origin", or prefixed, like "origin/foo"), fetch
that remote before resolving the ref. Abort the checkout if the fetch
fails.

Also add a checkout.fetch config to enable this by default.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
    checkout: add --fetch to fetch remote before resolving start-point
    
     * Rename the config from checkout.autoFetch to checkout.fetch, so it
       matches the --fetch option name.
    
     * Rename the internal struct field from auto_fetch to fetch for
       consistency with the option and config names.
    
     * Reword the commit message to lead with the problem (forgetting 'git
       fetch' and ending up with a stale start-point) before describing the
       solution.
    
     * Document --fetch / --no-fetch in git-checkout and git-switch, and
       document checkout.fetch in the config reference.
    
     * Use "remote-tracking branch" instead of "remote-tracking ref" in the
       option help text.

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

Range-diff vs v1:

 1:  e2fa50ff40 ! 1:  13074c9fea checkout: add --fetch to fetch remote before resolving start-point
     @@ Metadata
       ## Commit message ##
          checkout: add --fetch to fetch remote before resolving start-point
      
     -    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.
     +    A common workflow is:
     +
     +        git fetch origin
     +        git checkout -b new_branch origin/some-branch
     +
     +    The first command exists purely 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.
     +
     +    Teach checkout (and switch) a --fetch flag that folds the two steps
     +    into one:
     +
     +        git checkout --fetch -b new_branch origin/some-branch
     +
     +    When --fetch is given and <start-point> names a configured remote
     +    (either bare, like "origin", or prefixed, like "origin/foo"), fetch
     +    that remote before resolving the ref. Abort the checkout if the fetch
     +    fails.
     +
     +    Also add a checkout.fetch config to enable this by default.
      
          Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
      
     + ## Documentation/config/checkout.adoc ##
     +@@ Documentation/config/checkout.adoc: commands or functionality in the future.
     + 	option in `git checkout` and `git switch`. See
     + 	linkgit:git-switch[1] and linkgit:git-checkout[1].
     + 
     ++`checkout.fetch`::
     ++	Provides the default value for the `--fetch` or `--no-fetch`
     ++	option in `git checkout` and `git switch`. See
     ++	linkgit:git-switch[1] and linkgit:git-checkout[1].
     ++
     + `checkout.workers`::
     + 	The number of parallel workers to use when updating the working tree.
     + 	The default is one, i.e. sequential execution. If set to a value less
     +
     + ## Documentation/git-checkout.adoc ##
     +@@ Documentation/git-checkout.adoc: linkgit:git-config[1].
     + The default behavior can be set via the `checkout.guess` configuration
     + variable.
     + 
     ++`--fetch`::
     ++`--no-fetch`::
     ++	If _<start-point>_ names a configured remote -- either bare,
     ++	like `origin` (which resolves to the remote's default branch),
     ++	or in _<remote>/<branch>_ form -- run `git fetch` on that
     ++	remote before resolving _<start-point>_. If the fetch fails,
     ++	the checkout is aborted and no local branch is created.
     +++
     ++The default behavior can be set via the `checkout.fetch` configuration
     ++variable.
     ++
     + `-l`::
     + 	Create the new branch's reflog; see linkgit:git-branch[1] for
     + 	details.
     +
     + ## Documentation/git-switch.adoc ##
     +@@ Documentation/git-switch.adoc: ambiguous but exists on the 'origin' remote. See also
     + The default behavior can be set via the `checkout.guess` configuration
     + variable.
     + 
     ++`--fetch`::
     ++`--no-fetch`::
     ++	If _<start-point>_ names a configured remote -- either bare,
     ++	like `origin` (which resolves to the remote's default branch),
     ++	or in _<remote>/<branch>_ form -- run `git fetch` on that
     ++	remote before resolving _<start-point>_. If the fetch fails,
     ++	the switch is aborted and no local branch is created.
     +++
     ++The default behavior can be set via the `checkout.fetch` configuration
     ++variable.
     ++
     + `-f`::
     + `--force`::
     + 	An alias for `--discard-changes`.
     +
       ## builtin/checkout.c ##
      @@
       #include "repo-settings.h"
     @@ builtin/checkout.c: struct checkout_opts {
       	int count_checkout_paths;
       	int overlay_mode;
       	int dwim_new_local_branch;
     -+	int auto_fetch;
     ++	int fetch;
       	int discard_changes;
       	int accept_ref;
       	int accept_pathspec;
     @@ builtin/checkout.c: static int git_checkout_config(const char *var, const char *
       		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);
     ++	if (!strcmp(var, "checkout.fetch")) {
     ++		opts->fetch = git_config_bool(var, value);
      +		return 0;
      +	}
       
     @@ builtin/checkout.c: static int checkout_main(int argc, const char **argv, const
      -					     &new_branch_info, opts, &rev);
      +		int n;
      +
     -+		if (opts->auto_fetch)
     ++		if (opts->fetch)
      +			fetch_remote_for_start_point(argv[0]);
      +
      +		n = parse_branchname_arg(argc, argv, dwim_ok, which_command,
     @@ builtin/checkout.c: 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_BOOL(0, "fetch", &opts.fetch,
     ++			 N_("fetch from the remote first if <start-point> is a remote-tracking branch")),
       		OPT_END()
       	};
       
     @@ builtin/checkout.c: 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_BOOL(0, "fetch", &opts.fetch,
     ++			 N_("fetch from the remote first if <start-point> is a remote-tracking branch")),
       		OPT_END()
       	};
       
     @@ t/t7201-co.sh: test_expect_success 'tracking info copied with autoSetupMerge=inh
      +	test_must_fail git rev-parse --verify refs/heads/bogus
      +'
      +
     -+test_expect_success 'checkout.autoFetch=true enables fetching without --fetch' '
     ++test_expect_success 'checkout.fetch=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 &&
     ++	git -c checkout.fetch=true checkout -b local_cfg fetch_upstream/fetch_cfg &&
      +	test_cmp_rev refs/remotes/fetch_upstream/fetch_cfg HEAD
      +'
      +


 Documentation/config/checkout.adoc |  5 +++
 Documentation/git-checkout.adoc    | 11 +++++++
 Documentation/git-switch.adoc      | 11 +++++++
 builtin/checkout.c                 | 48 ++++++++++++++++++++++++++--
 t/t7201-co.sh                      | 51 ++++++++++++++++++++++++++++++
 t/t9902-completion.sh              |  1 +
 6 files changed, 125 insertions(+), 2 deletions(-)

diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
index e35d212969..c95f72b38e 100644
--- a/Documentation/config/checkout.adoc
+++ b/Documentation/config/checkout.adoc
@@ -22,6 +22,11 @@ commands or functionality in the future.
 	option in `git checkout` and `git switch`. See
 	linkgit:git-switch[1] and linkgit:git-checkout[1].
 
+`checkout.fetch`::
+	Provides the default value for the `--fetch` or `--no-fetch`
+	option in `git checkout` and `git switch`. See
+	linkgit:git-switch[1] and linkgit:git-checkout[1].
+
 `checkout.workers`::
 	The number of parallel workers to use when updating the working tree.
 	The default is one, i.e. sequential execution. If set to a value less
diff --git a/Documentation/git-checkout.adoc b/Documentation/git-checkout.adoc
index 43ccf47cf6..f20e2f4c8c 100644
--- a/Documentation/git-checkout.adoc
+++ b/Documentation/git-checkout.adoc
@@ -201,6 +201,17 @@ linkgit:git-config[1].
 The default behavior can be set via the `checkout.guess` configuration
 variable.
 
+`--fetch`::
+`--no-fetch`::
+	If _<start-point>_ names a configured remote -- either bare,
+	like `origin` (which resolves to the remote's default branch),
+	or in _<remote>/<branch>_ form -- run `git fetch` on that
+	remote before resolving _<start-point>_. If the fetch fails,
+	the checkout is aborted and no local branch is created.
++
+The default behavior can be set via the `checkout.fetch` configuration
+variable.
+
 `-l`::
 	Create the new branch's reflog; see linkgit:git-branch[1] for
 	details.
diff --git a/Documentation/git-switch.adoc b/Documentation/git-switch.adoc
index 87707e9265..3826ed9066 100644
--- a/Documentation/git-switch.adoc
+++ b/Documentation/git-switch.adoc
@@ -110,6 +110,17 @@ ambiguous but exists on the 'origin' remote. See also
 The default behavior can be set via the `checkout.guess` configuration
 variable.
 
+`--fetch`::
+`--no-fetch`::
+	If _<start-point>_ names a configured remote -- either bare,
+	like `origin` (which resolves to the remote's default branch),
+	or in _<remote>/<branch>_ form -- run `git fetch` on that
+	remote before resolving _<start-point>_. If the fetch fails,
+	the switch is aborted and no local branch is created.
++
+The default behavior can be set via the `checkout.fetch` configuration
+variable.
+
 `-f`::
 `--force`::
 	An alias for `--discard-changes`.
diff --git a/builtin/checkout.c b/builtin/checkout.c
index e031e61886..b2a34f0f00 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,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.fetch")) {
+		opts->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->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.fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking branch")),
 		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.fetch,
+			 N_("fetch from the remote first if <start-point> is a remote-tracking branch")),
 		OPT_END()
 	};
 
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 9bcf7c0b40..f5729f0831 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.fetch=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.fetch=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

  parent reply	other threads:[~2026-04-25 18:12 UTC|newest]

Thread overview: 35+ 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-04-24 17:38 ` [PATCH] checkout: add --fetch to fetch remote before resolving start-point 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 ` Harald Nordgren via GitGitGadget [this message]
2026-04-26  7:24   ` [PATCH v3] checkout: add --fetch to fetch remote before resolving start-point 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-05-08 22:52           ` [PATCH v7] checkout: extend --track with a "fetch" mode to refresh start-point 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-12 10:55               ` [PATCH v9] " 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.v2.git.git.1777140755373.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=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.