From: "Wyatt Holden via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: gitster@pobox.com, stolee@gmail.com, wyattj.holden@gmail.com,
Wyatt Holden <wyattholden@microsoft.com>
Subject: [PATCH 1/3] checkout: make orphaned commit checks configurable
Date: Tue, 10 Nov 2026 19:11:58 +0000 [thread overview]
Message-ID: <6aad1a1cd34a65939be6f64a0d8950e0bc23870a.1794337920.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2264.git.1794337920.gitgitgadget@gmail.com>
From: Wyatt Holden <wyattholden@microsoft.com>
When leaving a detached HEAD, checkout enumerates refs and walks
history to warn about commits left behind. This work can dominate
checkout time even after the working tree has been updated.
In an automated checkout of an internal monorepo, unpack_trees ended
about 12 seconds into a roughly 204-second run. Stack dumps captured
the main thread in orphaned_commit_warning(), enumerating loose refs
and opening ref files. Using --quiet reduced the observed checkout
time to about 14 seconds, but also suppressed unrelated output.
These observations motivate an opt-out; they do not establish how
much of the delay came from enumeration versus history traversal.
Add checkout.orphanedCommitWarning, enabled by default, so checkout
and switch can skip the check while retaining normal checkout output.
Automated callers can opt out when they do not need a warning about
leaving detached commits behind.
Follow the configuration precedent of 06b324c1d71 (status: add
status.aheadbehind setting, 2019-06-18). Unlike status, introduce no
dedicated command-line option; callers can use git -c for a one-off
override.
Add a Trace2 region around the check to make its cost visible in
future investigations and to verify that disabling the setting skips
the routine rather than merely hiding its warning. Document the
setting and test both checkout and switch.
Signed-off-by: Wyatt Holden <wyattholden@microsoft.com>
---
Documentation/config/checkout.adoc | 11 +++++++++++
builtin/checkout.c | 18 ++++++++++++++++--
t/t2020-checkout-detach.sh | 29 +++++++++++++++++++++++++++++
3 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/Documentation/config/checkout.adoc b/Documentation/config/checkout.adoc
index e35d212969..cacd10449a 100644
--- a/Documentation/config/checkout.adoc
+++ b/Documentation/config/checkout.adoc
@@ -22,6 +22,17 @@ commands or functionality in the future.
option in `git checkout` and `git switch`. See
linkgit:git-switch[1] and linkgit:git-checkout[1].
+`checkout.orphanedCommitWarning`::
+ When leaving a detached `HEAD`, linkgit:git-checkout[1] and
+ linkgit:git-switch[1] check whether any commits are being left
+ behind that are not reachable from any ref or the new `HEAD`,
+ and warn if so. This check can be expensive in large repositories.
+ Set this variable to `false` to skip the check and its warning
+ without suppressing other checkout output. Defaults to `true`.
++
+When disabled, commits left behind can still be found in the `HEAD`
+reflog until its entries expire. See linkgit:git-reflog[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/builtin/checkout.c b/builtin/checkout.c
index c0f0d2c700..7cc9e97612 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -51,6 +51,7 @@ struct checkout_opts {
int patch_interhunk_context;
int auto_advance;
int quiet;
+ int orphaned_commit_warning;
int merge;
int force;
int force_detach;
@@ -99,6 +100,7 @@ struct checkout_opts {
.patch_context = -1, \
.patch_interhunk_context = -1, \
.auto_advance = 1, \
+ .orphaned_commit_warning = 1, \
}
#define MERGE_WORKING_TREE_UNPACK_FAILED (-2)
@@ -1132,6 +1134,7 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
struct rev_info revs;
struct object *object = &old_commit->object;
+ trace2_region_enter("checkout", "orphaned_commit_warning", the_repository);
repo_init_revisions(the_repository, &revs, NULL);
setup_revisions(0, NULL, &revs, NULL);
@@ -1155,6 +1158,7 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
/* Clean up objects used, as they will be reused. */
repo_clear_commit_marks(the_repository, ALL_REV_FLAGS);
release_revisions(&revs);
+ trace2_region_leave("checkout", "orphaned_commit_warning", the_repository);
}
static int switch_branches(const struct checkout_opts *opts,
@@ -1253,8 +1257,14 @@ static int switch_branches(const struct checkout_opts *opts,
}
}
- if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
- orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit);
+ if (!opts->quiet && !old_branch_info.path && old_branch_info.commit &&
+ new_branch_info->commit != old_branch_info.commit) {
+ if (opts->orphaned_commit_warning)
+ orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit);
+ else
+ describe_detached_head(_("Previous HEAD position was"),
+ old_branch_info.commit);
+ }
if (autostash_res == STASH_APPLY_CONFLICT && !opts->quiet)
fputc('\n', stderr);
@@ -1295,6 +1305,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.orphanedcommitwarning")) {
+ opts->orphaned_commit_warning = git_config_bool(var, value);
+ return 0;
+ }
if (starts_with(var, "submodule."))
return git_default_submodule_config(var, value, NULL);
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index 28bbbe6c05..d21dbd5b72 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -164,6 +164,35 @@ test_expect_success 'checkout does not warn leaving reachable commit' '
check_no_orphan_warning stderr
'
+for command in checkout switch
+do
+ test_expect_success "$command honors checkout.orphanedCommitWarning" '
+ git checkout --detach "$orphan2" &&
+ GIT_TRACE2_EVENT="$(pwd)/trace-enabled-$command" \
+ git -c checkout.orphanedCommitWarning=true \
+ $command main 2>stderr &&
+ check_orphan_warning stderr "2 commits" &&
+ test_grep "\"label\":\"orphaned_commit_warning\"" \
+ trace-enabled-$command &&
+
+ git checkout --detach "$orphan2" &&
+ GIT_TRACE2_EVENT="$(pwd)/trace-disabled-$command" \
+ git -c checkout.orphanedCommitWarning=false \
+ $command main 2>stderr &&
+ check_no_orphan_warning stderr &&
+ test_grep "Switched to branch" stderr &&
+ test_grep ! "\"label\":\"orphaned_commit_warning\"" \
+ trace-disabled-$command &&
+ check_not_detached
+ '
+
+ test_expect_success "$command rejects invalid checkout.orphanedCommitWarning" '
+ test_must_fail git -c checkout.orphanedCommitWarning=invalid \
+ $command main 2>stderr &&
+ test_grep "bad boolean config value.*checkout.orphanedcommitwarning" stderr
+ '
+done
+
cat >expect <<'EOF'
Your branch is behind 'main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
--
gitgitgadget
next prev parent reply other threads:[~2026-11-10 19:12 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` Wyatt Holden via GitGitGadget [this message]
2026-11-10 22:05 ` [PATCH 1/3] checkout: make " Junio C Hamano
2026-11-10 19:11 ` [PATCH 2/3] checkout: advise when orphaned commit checks are slow Wyatt Holden via GitGitGadget
2026-11-10 20:48 ` Derrick Stolee
2026-11-10 22:22 ` Junio C Hamano
2026-11-11 18:52 ` Junio C Hamano
2026-11-10 19:12 ` [PATCH 3/3] scalar: disable orphaned commit checks by default Wyatt Holden via GitGitGadget
2026-11-10 20:57 ` [PATCH 0/3] Make orphaned commit checks configurable Derrick Stolee
2026-11-10 22:29 ` Junio C Hamano
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=6aad1a1cd34a65939be6f64a0d8950e0bc23870a.1794337920.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=stolee@gmail.com \
--cc=wyattholden@microsoft.com \
--cc=wyattj.holden@gmail.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox