* [PATCH 0/3] Make orphaned commit checks configurable
@ 2026-11-10 19:11 Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` [PATCH 1/3] checkout: make " Wyatt Holden via GitGitGadget
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Wyatt Holden via GitGitGadget @ 2026-11-10 19:11 UTC (permalink / raw)
To: git; +Cc: gitster, stolee, wyattj.holden
Hi there - my name's Wyatt Holden and I'm a Software Engineer at Microsoft
:) I've been working with Derrick Stolee over the past few weeks on
optimizing git in the Office mono-repo. We found some issues around slow
checkouts on mono-repos in an automated environment:
When leaving a detached HEAD, git checkout and git switch
call orphaned_commit_warning() to warn about commits that are not reachable
from any existing ref or the destination HEAD. This requires enumerating
refs and walking commit history, which can be expensive in large
repositories.
In an internal mono-repo, Trace2 showed tree unpacking finishing
approximately 12 seconds into checkout, but the process continuing for
roughly another 193 seconds. A stack dump captured the main thread inside
orphaned_commit_warning(), enumerating loose refs and opening ref files.
Running checkout with --quiet, which bypasses this routine, reduced the
observed runtime to approximately 14 seconds. However, --quiet also
suppresses unrelated output.
This three-commit series adds a targeted opt-out, advertises it when the
check is slow, and enables it in Scalar:
1. checkout: make orphaned commit checks configurable Add
checkout.orphanedCommitWarning , enabled by default, for both checkout
and switch. Setting it to false skips the reachability check while
retaining the previous HEAD description and normal checkout output. Add
a Trace2 region to expose the check’s duration and tests verifying that
disabling the setting bypasses the routine.
2. checkout: advise when orphaned commit checks are slow When the check
takes more than two seconds, suggest the opt-out and explain that
disabling it also removes the warning about commits left behind. Control
this advice independently through advice.checkoutOrphanedCommitWarning.
Test advice and suppression without relying on wall-clock delays.
3. scalar: disable orphaned commit checks by default Add
checkout.orphanedCommitWarning=false to Scalar’s recommended
configuration using its existing set-if-missing mechanism, preserving
explicit user settings during clone, register, and reconfigure. Document
the tradeoff and reflog recovery; rely on existing Scalar tests for
configuration-preservation behavior.
Ordinary Git retains its existing default behavior. Users who disable the
check lose the immediate warning about commits left behind; those commits
can still be recovered through the HEAD reflog while the relevant entries
and objects remain available.
Wyatt Holden (3):
checkout: make orphaned commit checks configurable
checkout: advise when orphaned commit checks are slow
scalar: disable orphaned commit checks by default
Documentation/config/advice.adoc | 6 +++
Documentation/config/checkout.adoc | 11 +++++
Documentation/scalar.adoc | 7 +++
advice.c | 1 +
advice.h | 1 +
builtin/checkout.c | 31 +++++++++++++-
scalar.c | 1 +
t/README | 5 +++
t/t2020-checkout-detach.sh | 69 ++++++++++++++++++++++++++++++
9 files changed, 130 insertions(+), 2 deletions(-)
base-commit: 6de20f6092dcf9bdb1c8efe03db4b70c82b423dd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2264%2Fwyatt-holden%2Fwyattholden%2Fcheckout-orphaned-commit-warning-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2264/wyatt-holden/wyattholden/checkout-orphaned-commit-warning-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2264
--
gitgitgadget
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 1/3] checkout: make orphaned commit checks configurable
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
2026-11-10 22:05 ` Junio C Hamano
2026-11-10 19:11 ` [PATCH 2/3] checkout: advise when orphaned commit checks are slow Wyatt Holden via GitGitGadget
` (3 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Wyatt Holden via GitGitGadget @ 2026-11-10 19:11 UTC (permalink / raw)
To: git; +Cc: gitster, stolee, wyattj.holden, Wyatt Holden
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
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 2/3] checkout: advise when orphaned commit checks are slow
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` [PATCH 1/3] checkout: make " Wyatt Holden via GitGitGadget
@ 2026-11-10 19:11 ` Wyatt Holden via GitGitGadget
2026-11-10 20:48 ` Derrick Stolee
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
` (2 subsequent siblings)
4 siblings, 2 replies; 10+ messages in thread
From: Wyatt Holden via GitGitGadget @ 2026-11-10 19:11 UTC (permalink / raw)
To: git; +Cc: gitster, stolee, wyattj.holden, Wyatt Holden
From: Wyatt Holden <wyattholden@microsoft.com>
The orphaned-commit check can take much longer than updating the
working tree. Users who encounter this cost need a way to discover
the checkout.orphanedCommitWarning setting.
Measure the complete check and suggest disabling it when it takes
more than two seconds. Explain that opting out also removes the
warning about commits left behind, and allow the advice itself to be
disabled with advice.checkoutOrphanedCommitWarning.
Follow the slow-operation advice introduced by:
0a53561a622 (status: warn when a/b calculation takes too long, 2019-06-18)
Unlike that status advice, recommend a configuration setting rather
than a dedicated command-line option.
Keep a test hook to exercise advice and suppression without relying
on wall-clock delays. Force the advice condition without replacing
the measured duration, and check the formatted duration rather than
a fabricated fixed value.
Signed-off-by: Wyatt Holden <wyattholden@microsoft.com>
---
Documentation/config/advice.adoc | 6 +++++
advice.c | 1 +
advice.h | 1 +
builtin/checkout.c | 13 +++++++++++
t/README | 5 ++++
t/t2020-checkout-detach.sh | 40 ++++++++++++++++++++++++++++++++
6 files changed, 66 insertions(+)
diff --git a/Documentation/config/advice.adoc b/Documentation/config/advice.adoc
index 3d91c90eda..eecf72d8d1 100644
--- a/Documentation/config/advice.adoc
+++ b/Documentation/config/advice.adoc
@@ -38,6 +38,12 @@ all advice messages.
configuration variable for how to set a given remote
to be used by default in some situations where this
advice would be printed.
+ checkoutOrphanedCommitWarning::
+ Shown when linkgit:git-checkout[1] or linkgit:git-switch[1]
+ takes more than 2 seconds to check for commits left behind
+ when leaving a detached `HEAD`. Suggests setting
+ `checkout.orphanedCommitWarning` to `false` to skip the check.
+ Disabling this advice does not disable the check itself.
commitBeforeMerge::
Shown when linkgit:git-merge[1] refuses to
merge to avoid overwriting local changes.
diff --git a/advice.c b/advice.c
index 401d047391..8142d73b5e 100644
--- a/advice.c
+++ b/advice.c
@@ -50,6 +50,7 @@ static struct {
[ADVICE_AMBIGUOUS_FETCH_REFSPEC] = { "ambiguousFetchRefspec" },
[ADVICE_AM_WORK_DIR] = { "amWorkDir" },
[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
+ [ADVICE_CHECKOUT_ORPHANED_COMMIT_WARNING] = { "checkoutOrphanedCommitWarning" },
[ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
[ADVICE_DEFAULT_BRANCH_NAME] = { "defaultBranchName" },
[ADVICE_DETACHED_HEAD] = { "detachedHead" },
diff --git a/advice.h b/advice.h
index 3f0b4f0485..7dd1fe109c 100644
--- a/advice.h
+++ b/advice.h
@@ -17,6 +17,7 @@ enum advice_type {
ADVICE_AMBIGUOUS_FETCH_REFSPEC,
ADVICE_AM_WORK_DIR,
ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,
+ ADVICE_CHECKOUT_ORPHANED_COMMIT_WARNING,
ADVICE_COMMIT_BEFORE_MERGE,
ADVICE_DEFAULT_BRANCH_NAME, /* To be retired sometime after Git 3.0 */
ADVICE_DETACHED_HEAD,
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 7cc9e97612..1e94280d35 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -35,6 +35,7 @@
#include "strvec.h"
#include "submodule.h"
#include "symlinks.h"
+#include "trace.h"
#include "trace2.h"
#include "tree.h"
#include "tree-walk.h"
@@ -1133,6 +1134,8 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
{
struct rev_info revs;
struct object *object = &old_commit->object;
+ uint64_t t_begin = getnanotime();
+ uint64_t t_delta_in_ms;
trace2_region_enter("checkout", "orphaned_commit_warning", the_repository);
repo_init_revisions(the_repository, &revs, NULL);
@@ -1159,6 +1162,16 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne
repo_clear_commit_marks(the_repository, ALL_REV_FLAGS);
release_revisions(&revs);
trace2_region_leave("checkout", "orphaned_commit_warning", the_repository);
+
+ t_delta_in_ms = (getnanotime() - t_begin) / 1000000;
+ if (t_delta_in_ms > 2000 ||
+ git_env_bool("GIT_TEST_CHECKOUT_DELAY_WARNING", 0))
+ advise_if_enabled(ADVICE_CHECKOUT_ORPHANED_COMMIT_WARNING,
+ _("Checking for orphaned commits took %.2f seconds.\n"
+ "You can use 'git config set checkout.orphanedCommitWarning false'\n"
+ "to skip this check, but you will no longer be warned when leaving\n"
+ "commits behind that are not connected to any of your branches."),
+ t_delta_in_ms / 1000.0);
}
static int switch_branches(const struct checkout_opts *opts,
diff --git a/t/README b/t/README
index fd7e8504b5..577f02732e 100644
--- a/t/README
+++ b/t/README
@@ -371,6 +371,11 @@ test suite. Accept any boolean values that are accepted by git-config.
GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
default to n.
+GIT_TEST_CHECKOUT_DELAY_WARNING=<boolean>, when true, forces the
+slow orphaned-commit check advice to be considered regardless of the
+elapsed time. It does not change the measured duration or override
+advice settings.
+
GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon
pack-objects code path where there are more than 1024 packs even if
the actual number of packs in repository is below this limit. Accept
diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
index d21dbd5b72..7dfaaab1b8 100755
--- a/t/t2020-checkout-detach.sh
+++ b/t/t2020-checkout-detach.sh
@@ -186,6 +186,46 @@ do
check_not_detached
'
+ test_expect_success "$command advises when the orphan check is slow" '
+ git checkout --detach "$orphan2" &&
+ GIT_TEST_CHECKOUT_DELAY_WARNING=true \
+ git $command main 2>stderr &&
+ check_orphan_warning stderr "2 commits" &&
+ test_grep -E "^hint: Checking for orphaned commits took [0-9]+[.][0-9]{2} seconds[.]$" stderr &&
+ test_grep "git config set checkout.orphanedCommitWarning false" stderr &&
+ test_grep "git config set advice.checkoutOrphanedCommitWarning false" stderr
+ '
+
+ test_expect_success "$command can suppress slow orphan check advice" '
+ git checkout --detach "$orphan2" &&
+ GIT_TEST_CHECKOUT_DELAY_WARNING=true \
+ git -c advice.checkoutOrphanedCommitWarning=false \
+ $command main 2>stderr &&
+ check_orphan_warning stderr "2 commits" &&
+ test_grep ! "^hint:" stderr &&
+
+ git checkout --detach "$orphan2" &&
+ GIT_TEST_CHECKOUT_DELAY_WARNING=true GIT_ADVICE=0 \
+ git $command main 2>stderr &&
+ check_orphan_warning stderr "2 commits" &&
+ test_grep ! "^hint:" stderr
+ '
+
+ test_expect_success "$command does not advise when the orphan check is skipped" '
+ git checkout --detach "$orphan2" &&
+ GIT_TEST_CHECKOUT_DELAY_WARNING=true \
+ git -c checkout.orphanedCommitWarning=false \
+ $command main 2>stderr &&
+ check_no_orphan_warning stderr &&
+ test_grep ! "^hint:" stderr &&
+
+ git checkout --detach "$orphan2" &&
+ GIT_TEST_CHECKOUT_DELAY_WARNING=true \
+ git $command --quiet main 2>stderr &&
+ test_must_be_empty stderr &&
+ check_not_detached
+ '
+
test_expect_success "$command rejects invalid checkout.orphanedCommitWarning" '
test_must_fail git -c checkout.orphanedCommitWarning=invalid \
$command main 2>stderr &&
--
gitgitgadget
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH 3/3] scalar: disable orphaned commit checks by default
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` [PATCH 1/3] checkout: make " Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` [PATCH 2/3] checkout: advise when orphaned commit checks are slow Wyatt Holden via GitGitGadget
@ 2026-11-10 19:12 ` 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
4 siblings, 0 replies; 10+ messages in thread
From: Wyatt Holden via GitGitGadget @ 2026-11-10 19:12 UTC (permalink / raw)
To: git; +Cc: gitster, stolee, wyattj.holden, Wyatt Holden
From: Wyatt Holden <wyattholden@microsoft.com>
Checking for commits left behind when leaving a detached HEAD can
be expensive in large repositories. Add
checkout.orphanedCommitWarning=false to Scalar's recommended settings
to avoid this foreground work while preserving normal checkout output.
Use the existing set-if-missing behavior so explicit user choices
are preserved during clone, register, and reconfigure. Document the
loss of the immediate orphan warning and the availability of reflog
recovery. Existing Scalar tests cover configuration preservation,
so no additional inventory of recommended settings is needed.
Signed-off-by: Wyatt Holden <wyattholden@microsoft.com>
---
Documentation/scalar.adoc | 7 +++++++
scalar.c | 1 +
2 files changed, 8 insertions(+)
diff --git a/Documentation/scalar.adoc b/Documentation/scalar.adoc
index 5252fb134a..d2b8c9a6ca 100644
--- a/Documentation/scalar.adoc
+++ b/Documentation/scalar.adoc
@@ -214,6 +214,13 @@ am.keepCR=true::
and non-Windows platforms and keeping carriage return (`\r`) characters
in certain workflows.
+checkout.orphanedCommitWarning=false::
+ This disables the reachability check that warns about commits left
+ behind when `git checkout` or `git switch` leaves a detached `HEAD`.
+ The check can be expensive in large repositories. Other checkout
+ output is unchanged, and commits left behind can still be found
+ in the `HEAD` reflog until its entries expire.
+
commitGraph.changedPaths=true::
This setting helps the background maintenance steps that compute the
serialized commit-graph to also store changed-path Bloom filters. This
diff --git a/scalar.c b/scalar.c
index a80d8ee3ff..7f851f5610 100644
--- a/scalar.c
+++ b/scalar.c
@@ -138,6 +138,7 @@ static int set_recommended_config(int reconfigure)
*/
struct scalar_config config[] = {
{ "am.keepCR", "true" },
+ { "checkout.orphanedCommitWarning", "false" },
{ "commitGraph.changedPaths", "true" },
{ "commitGraph.generationVersion", "1" },
{ "core.autoCRLF", "false" },
--
gitgitgadget
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] checkout: advise when orphaned commit checks are slow
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
1 sibling, 1 reply; 10+ messages in thread
From: Derrick Stolee @ 2026-11-10 20:48 UTC (permalink / raw)
To: Wyatt Holden via GitGitGadget, git; +Cc: gitster, wyattj.holden, Wyatt Holden
On 11/10/2026 2:11 PM, Wyatt Holden via GitGitGadget wrote:
> [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
> + [ADVICE_CHECKOUT_ORPHANED_COMMIT_WARNING] = { "checkoutOrphanedCommitWarning" },
> [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
It looks like we have a slight whitespace alignment problem here.
Thanks,
-Stolee
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 0/3] Make orphaned commit checks configurable
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
` (2 preceding siblings ...)
2026-11-10 19:12 ` [PATCH 3/3] scalar: disable orphaned commit checks by default Wyatt Holden via GitGitGadget
@ 2026-11-10 20:57 ` Derrick Stolee
2026-11-10 22:29 ` Junio C Hamano
4 siblings, 0 replies; 10+ messages in thread
From: Derrick Stolee @ 2026-11-10 20:57 UTC (permalink / raw)
To: Wyatt Holden via GitGitGadget, git; +Cc: gitster, wyattj.holden
On 11/10/2026 2:11 PM, Wyatt Holden via GitGitGadget wrote:
> Hi there - my name's Wyatt Holden and I'm a Software Engineer at Microsoft
> :) I've been working with Derrick Stolee over the past few weeks on
> optimizing git in the Office mono-repo. We found some issues around slow
> checkouts on mono-repos in an automated environment:
Welcome, Wyatt! (I was prepared for your series.)
> When leaving a detached HEAD, git checkout and git switch
> call orphaned_commit_warning() to warn about commits that are not reachable
> from any existing ref or the destination HEAD. This requires enumerating
> refs and walking commit history, which can be expensive in large
> repositories.
>
> In an internal mono-repo, Trace2 showed tree unpacking finishing
> approximately 12 seconds into checkout, but the process continuing for
> roughly another 193 seconds. A stack dump captured the main thread inside
> orphaned_commit_warning(), enumerating loose refs and opening ref files.
> Running checkout with --quiet, which bypasses this routine, reduced the
> observed runtime to approximately 14 seconds. However, --quiet also
> suppresses unrelated output.
This example shows a significant pain caused by this check, but we also
want to be able to see progress indicators over time in our logs, so
using --quiet isn't preferred, long term.
> This three-commit series adds a targeted opt-out, advertises it when the
> check is slow, and enables it in Scalar:
I did some pre-review with Wyatt (but found one nitpick in my on-list
re-review) so I'm biased. Further bias: I recommended that he follow
the model of the status.aheadBehind config [1] and advice.statusAheadBehind
[2] patches as prior art.
[1] https://lore.kernel.org/git/2f0f2a2df9410947b985d83a924d2549d62bb65f.1560889284.git.gitgitgadget@gmail.com/
[2] https://lore.kernel.org/git/c1056545b617b89e881f5991beb2fe0d860de740.1560889284.git.gitgitgadget@gmail.com/
And also I added status.aheadBehind to Scalar's recommended config in
its very first version [3]. Wyatt has updated the Scalar config docs
that justify the recommendation, which was introduced only recently [4].
[3] https://lore.kernel.org/git/4439ab4de0bc3f48a6bdcf4b5165b16fad792ebd.1638538470.git.gitgitgadget@gmail.com/
[4] https://lore.kernel.org/git/f062b0e0773b9d168c031bb1f35538665394a077.1765552528.git.gitgitgadget@gmail.com/
So I give my support publicly for Wyatt's updates.
Thanks,
-Stolee
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 1/3] checkout: make orphaned commit checks configurable
2026-11-10 19:11 ` [PATCH 1/3] checkout: make " Wyatt Holden via GitGitGadget
@ 2026-11-10 22:05 ` Junio C Hamano
0 siblings, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2026-11-10 22:05 UTC (permalink / raw)
To: Wyatt Holden via GitGitGadget; +Cc: git, stolee, wyattj.holden, Wyatt Holden
"Wyatt Holden via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 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.
Interesting.
I often am on a detached HEAD but never realized this particular
check was expensive. The size of changes between the HEAD and the
next branch would affect the time it needs to update the working
tree (the larger the difference, the more expensive to complete the
branch switching). What factors are involved in making this
"orphaned commits" check more expensive? The total number of
branches? The length of commit chain we are leaving behind? I am
trying to see if this is something we can aim to optimize or if it
is a lost cause and a cop-out to introduce an option to disable the
check altogether is the best we can do.
> 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.
So, in short, the answer to my earlier question is "we don't know".
> 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.
Sounds sensible.
> 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.
I am not sure how justifiable the design decision to leave the
command line option out is.
The automation can run with "-q" to omit the overhead altogether, as
well as squelching any other output the automation would not read
anyway, so I would not worry too much about them.
But for human users, what are they losing by passing "-q" when they
do not want to pay the cost of this check because we are not giving
them an easy to use command line option? What other output are they
declining by "--quiet"? "The following paths have local changes"
list? Anything else?
> 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.
Sounds sensible.
> @@ -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);
> }
Quite straightforward. There is no early return in the middle, so
entering at the beginning with leaving at the end is the only change
necessary.
> @@ -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);
> + }
OK. The call into suggest_reattach() via orphaned_commit_warning()
is the more expensive part, and it also is where "Previous HEAD
was" comes, which we do not want to lose. It makes sense.
Another thing I noticed is that the loop in suggest_reattach() stops
showing each commit after showing ORPHAN_CUTOFF but keeps counting.
I wonder if it becomes palatable to introduce a configuration to
stop it early, instead of disabling the orphaned_commit_warning()
altogether?
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] checkout: advise when orphaned commit checks are slow
2026-11-10 20:48 ` Derrick Stolee
@ 2026-11-10 22:22 ` Junio C Hamano
0 siblings, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2026-11-10 22:22 UTC (permalink / raw)
To: Derrick Stolee
Cc: Wyatt Holden via GitGitGadget, git, wyattj.holden, Wyatt Holden
Derrick Stolee <stolee@gmail.com> writes:
> On 11/10/2026 2:11 PM, Wyatt Holden via GitGitGadget wrote:
>
>> [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName" },
>> + [ADVICE_CHECKOUT_ORPHANED_COMMIT_WARNING] = { "checkoutOrphanedCommitWarning" },
>> [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge" },
>
> It looks like we have a slight whitespace alignment problem here.
>
> Thanks,
> -Stolee
Good eyes. What the patch aims does sound sensible to me, though.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 0/3] Make orphaned commit checks configurable
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
` (3 preceding siblings ...)
2026-11-10 20:57 ` [PATCH 0/3] Make orphaned commit checks configurable Derrick Stolee
@ 2026-11-10 22:29 ` Junio C Hamano
4 siblings, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2026-11-10 22:29 UTC (permalink / raw)
To: Wyatt Holden via GitGitGadget; +Cc: git, stolee, wyattj.holden
"Wyatt Holden via GitGitGadget" <gitgitgadget@gmail.com> writes:
> When leaving a detached HEAD, git checkout and git switch
> call orphaned_commit_warning() to warn about commits that are not reachable
> from any existing ref or the destination HEAD. This requires enumerating
> refs and walking commit history, which can be expensive in large
> repositories.
I found some design choices made in 1/3 questionable but overall the
series is coherently done and very easy to understand _why_ it wants
to do what it does. Nice work.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/3] checkout: advise when orphaned commit checks are slow
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-11 18:52 ` Junio C Hamano
1 sibling, 0 replies; 10+ messages in thread
From: Junio C Hamano @ 2026-11-11 18:52 UTC (permalink / raw)
To: Wyatt Holden via GitGitGadget; +Cc: git, stolee, wyattj.holden, Wyatt Holden
"Wyatt Holden via GitGitGadget" <gitgitgadget@gmail.com> writes:
> diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh
> index d21dbd5b72..7dfaaab1b8 100755
> --- a/t/t2020-checkout-detach.sh
> +++ b/t/t2020-checkout-detach.sh
> @@ -186,6 +186,46 @@ do
> check_not_detached
> '
>
> + test_expect_success "$command advises when the orphan check is slow" '
> + git checkout --detach "$orphan2" &&
> + GIT_TEST_CHECKOUT_DELAY_WARNING=true \
> + git $command main 2>stderr &&
> + check_orphan_warning stderr "2 commits" &&
> + test_grep -E "^hint: Checking for orphaned commits took [0-9]+[.][0-9]{2} seconds[.]$" stderr &&
> + test_grep "git config set checkout.orphanedCommitWarning false" stderr &&
> + test_grep "git config set advice.checkoutOrphanedCommitWarning false" stderr
> + '
This test will not pass in the modern world where the advice message
suggests to use "git config set --global advice.FOO false".
It probably makes sense to grep for something looser like
"git config .* advice\.checkoutOrphanedCommitWarning"
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-11 18:52 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-10 19:11 [PATCH 0/3] Make orphaned commit checks configurable Wyatt Holden via GitGitGadget
2026-11-10 19:11 ` [PATCH 1/3] checkout: make " Wyatt Holden via GitGitGadget
2026-11-10 22:05 ` 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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox