Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Documentation/config/advice.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions Documentation/config/checkout.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions Documentation/scalar.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions advice.c
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
1 change: 1 addition & 0 deletions advice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
31 changes: 29 additions & 2 deletions builtin/checkout.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -51,6 +52,7 @@ struct checkout_opts {
int patch_interhunk_context;
int auto_advance;
int quiet;
int orphaned_commit_warning;
int merge;
int force;
int force_detach;
Expand Down Expand Up @@ -99,6 +101,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)
Expand Down Expand Up @@ -1131,7 +1134,10 @@ 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);
setup_revisions(0, NULL, &revs, NULL);

Expand All @@ -1155,6 +1161,17 @@ 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);

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,
Expand Down Expand Up @@ -1253,8 +1270,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);
Expand Down Expand Up @@ -1295,6 +1318,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;
}
Comment thread
wyatt-holden marked this conversation as resolved.

if (starts_with(var, "submodule."))
return git_default_submodule_config(var, value, NULL);
Expand Down
1 change: 1 addition & 0 deletions scalar.c
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
5 changes: 5 additions & 0 deletions t/README
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions t/t2020-checkout-detach.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,75 @@ 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 &&
Comment thread
wyatt-holden marked this conversation as resolved.

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 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 &&
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)
Expand Down
Loading