diff mbox series

[2/3] worktree: warn when removing a worktree with orphan commits

Message ID 1897dff1-bb4d-9715-dd1c-86763c052589@gmail.com (mailing list archive)
State New, archived
Headers show
Series warn when unreachable commits are left behind | expand

Commit Message

Rubén Justo April 22, 2023, 10:19 p.m. UTC
While working in a detached worktree, the user can create some commits
which won't be automatically connected to any ref.

Eventually, that worktree can be removed and, if the user has not
created any ref connected to the HEAD in that worktree (e.g. branch,
tag), those commits will become unreachable.

Let's issue a warning to remind the user for safety, when deleting a
worktree whose HEAD is not connected to an existing ref.

Let's also add an option to modify the message we show in
orphaned_commit_warning(): "Previous HEAD position was..."; allowing to
omit the word "Previous" as it may cause confusion, erroneously
suggesting that there is a "Current HEAD" while the worktree has been
removed.

Signed-off-by: Rubén Justo <rjusto@gmail.com>
---
 builtin/checkout.c       |  2 +-
 builtin/worktree.c       |  8 ++++++++
 checkout.c               |  7 +++++--
 checkout.h               |  3 ++-
 t/t2403-worktree-move.sh | 10 ++++++++++
 5 files changed, 26 insertions(+), 4 deletions(-)

Comments

Junio C Hamano April 24, 2023, 8:28 p.m. UTC | #1
Rubén Justo <rjusto@gmail.com> writes:

> While working in a detached worktree, the user can create some commits
> which won't be automatically connected to any ref.
>
> Eventually, that worktree can be removed and, if the user has not
> created any ref connected to the HEAD in that worktree (e.g. branch,
> tag), those commits will become unreachable.

The latter half of the first sentence feels a bit awkward, primarily
it sounds as if it almost wants to hint that it is good if we
connected these commits to some ref automatically, and it is far
from obvious why it is a good idea.  Perhaps

    ... the user can create some commits on detached HEAD, that are
    not connected to any ref.  If the user hasn't pointed at these
    commits by refs before removing the worktree, those commits will
    become unreachable.

That would be in line with the comment you moved in 1/3 that
describes why orphaned_commit_warning() helper is there, i.e.

    /*
     * We are about to leave commit that was at the tip of a detached
     * HEAD.  If it is not reachable from any ref, this is the last chance
     * for the user to do so without resorting to reflog.
     */

> Let's issue a warning to remind the user for safety, when deleting a
> worktree whose HEAD is not connected to an existing ref.

Good idea.  "Let's issue" -> "Issue" (or "Give", "Show").

> Let's also add an option to modify the message we show in
> orphaned_commit_warning(): "Previous HEAD position was..."; allowing to
> omit the word "Previous" as it may cause confusion, erroneously
> suggesting that there is a "Current HEAD" while the worktree has been
> removed.

Yes, it is absolutely necessary to adjust the message if you are to
reuse the orphaned_commit_warning() helper so that it matches the
situation as the end-user experiences.

>  	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);
> +		orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit, 1);

The magic number "1" looks iffy.

> diff --git a/builtin/worktree.c b/builtin/worktree.c
> index a61bc32189..df269bccc8 100644
> --- a/builtin/worktree.c
> +++ b/builtin/worktree.c
> @@ -1138,6 +1138,14 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
>  
>  		ret |= delete_git_work_tree(wt);
>  	}
> +
> +	if (!wt->head_ref && !is_null_oid(&wt->head_oid)) {
> +		struct commit* wt_commit = lookup_commit_reference_gently(the_repository,

Asterisk sticks to the variable, not to type, in C.  If you write

	struct commit *pointer, structure;

it is clear only one is the pointer.  It misleads people if you wrote

	struct commit* one, two;

instead.

> +									  &wt->head_oid, 1);

Also, lines around here look overly long.  Would it help to fold the
line after the initialization assignment, i.e.

		struct commit *wt_commit =
			lookup_commit_reference_gently(the_repository, ...);


> +		if (wt_commit)
> +			orphaned_commit_warning(wt_commit, NULL, 0);

Again, the magic number "0" looks iffy.

> diff --git a/checkout.c b/checkout.c
> index 18e7362043..5f7b0b3c49 100644
> --- a/checkout.c
> +++ b/checkout.c
> @@ -171,7 +171,8 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
>   * HEAD.  If it is not reachable from any ref, this is the last chance
>   * for the user to do so without resorting to reflog.
>   */
> -void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit)
> +void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
> +			     int show_previous_position)
>  {
>  	struct rev_info revs;
>  	struct object *object = &old_commit->object;
> @@ -192,8 +193,10 @@ void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commi
>  		die(_("internal error in revision walk"));
>  	if (!(old_commit->object.flags & UNINTERESTING))
>  		suggest_reattach(old_commit, &revs);
> -	else
> +	else if (show_previous_position)
>  		describe_detached_head(_("Previous HEAD position was"), old_commit);
> +	else
> +		describe_detached_head(_("HEAD position was"), old_commit);

Can we think of a single way to phrase this batter?  It's not like
the reason why the user wants to save the orphaned history is
because it was at the PREVIOUS HEAD, or at the HEAD of a now-lost
working tree.  It is because the history leading to that commit is
now about to be lost.  So perhaps "history leading to commit X has
become unreachable" or something would apply to both situation and
we do not have to pass the mysterious "0"/"1" that are hardcoded?

If the situation were the opposite and there were many ways that
lead to lost history (i.e. not just the original "switch out of the
detached HEAD", we are now adding "discarding a worktree with HEAD
detached", and there may be more cases added in the future) that
need to be described differently, I would have instead suggested to
use an enum and use different phrasing for each case, but it does
not seem that the original "Previous HEAD position was" is so
superbly phrased that we do not want to lose it, and the second one
being added in the above hunk is not all that different.  If we can
get away with just a single universal message, it would make things
simpler.

> diff --git a/checkout.h b/checkout.h
> index c7dc056544..ee400376d5 100644
> --- a/checkout.h
> +++ b/checkout.h
> @@ -18,7 +18,8 @@ const char *unique_tracking_name(const char *name,
>   * HEAD.  If it is not reachable from any ref, this is the last chance
>   * for the user to do so without resorting to reflog.
>   */
> -void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit);
> +void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
> +			     int show_previous_position);
>  
>  void describe_detached_head(const char *msg, struct commit *commit);
>  #endif /* CHECKOUT_H */
> diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
> index 230a55e99a..f2756f7137 100755
> --- a/t/t2403-worktree-move.sh
> +++ b/t/t2403-worktree-move.sh
> @@ -247,4 +247,14 @@ test_expect_success 'not remove a repo with initialized submodule' '
>  	)
>  '
>  
> +test_expect_success 'warn when removing a worktree with orphan commits' '
> +	git worktree add --detach foo &&
> +	git -C foo commit -m one --allow-empty &&
> +	git -C foo commit -m two --allow-empty &&
> +	git worktree remove foo 2>err &&
> +	test_i18ngrep "you are leaving 2 commits behind" err &&
> +	test_i18ngrep ! "Previous HEAD position was" err
> +	test_i18ngrep "HEAD position was" err
> +'
> +
>  test_done
Rubén Justo April 26, 2023, 10:29 p.m. UTC | #2
On 24-abr-2023 13:28:14, Junio C Hamano wrote:
> Rubén Justo <rjusto@gmail.com> writes:
> 
> > While working in a detached worktree, the user can create some commits
> > which won't be automatically connected to any ref.
> >
> > Eventually, that worktree can be removed and, if the user has not
> > created any ref connected to the HEAD in that worktree (e.g. branch,
> > tag), those commits will become unreachable.
> 
> The latter half of the first sentence feels a bit awkward, primarily
> it sounds as if it almost wants to hint that it is good if we
> connected these commits to some ref automatically, and it is far
> from obvious why it is a good idea.  Perhaps
> 
>     ... the user can create some commits on detached HEAD, that are
>     not connected to any ref.  If the user hasn't pointed at these
>     commits by refs before removing the worktree, those commits will
>     become unreachable.
> 
> That would be in line with the comment you moved in 1/3 that
> describes why orphaned_commit_warning() helper is there, i.e.
> 
>     /*
>      * We are about to leave commit that was at the tip of a detached
>      * HEAD.  If it is not reachable from any ref, this is the last chance
>      * for the user to do so without resorting to reflog.
>      */
> 

OK.  I'll reword the message with that.

> > Let's issue a warning to remind the user for safety, when deleting a
> > worktree whose HEAD is not connected to an existing ref.
> 
> Good idea.  "Let's issue" -> "Issue" (or "Give", "Show").

OK.

> 
> > Let's also add an option to modify the message we show in
> > orphaned_commit_warning(): "Previous HEAD position was..."; allowing to
> > omit the word "Previous" as it may cause confusion, erroneously
> > suggesting that there is a "Current HEAD" while the worktree has been
> > removed.
> 
> Yes, it is absolutely necessary to adjust the message if you are to
> reuse the orphaned_commit_warning() helper so that it matches the
> situation as the end-user experiences.
> 
> >  	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);
> > +		orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit, 1);
> 
> The magic number "1" looks iffy.
> 
> > diff --git a/builtin/worktree.c b/builtin/worktree.c
> > index a61bc32189..df269bccc8 100644
> > --- a/builtin/worktree.c
> > +++ b/builtin/worktree.c
> > @@ -1138,6 +1138,14 @@ static int remove_worktree(int ac, const char **av, const char *prefix)
> >  
> >  		ret |= delete_git_work_tree(wt);
> >  	}
> > +
> > +	if (!wt->head_ref && !is_null_oid(&wt->head_oid)) {
> > +		struct commit* wt_commit = lookup_commit_reference_gently(the_repository,
> 
> Asterisk sticks to the variable, not to type, in C.  If you write
> 
> 	struct commit *pointer, structure;
> 
> it is clear only one is the pointer.  It misleads people if you wrote
> 
> 	struct commit* one, two;
> 
> instead.

OK, sorry.

> 
> > +									  &wt->head_oid, 1);
> 
> Also, lines around here look overly long.  Would it help to fold the
> line after the initialization assignment, i.e.
> 
> 		struct commit *wt_commit =
> 			lookup_commit_reference_gently(the_repository, ...);

OK.

> 
> 
> > +		if (wt_commit)
> > +			orphaned_commit_warning(wt_commit, NULL, 0);
> 
> Again, the magic number "0" looks iffy.
> 
> > diff --git a/checkout.c b/checkout.c
> > index 18e7362043..5f7b0b3c49 100644
> > --- a/checkout.c
> > +++ b/checkout.c
> > @@ -171,7 +171,8 @@ static void suggest_reattach(struct commit *commit, struct rev_info *revs)
> >   * HEAD.  If it is not reachable from any ref, this is the last chance
> >   * for the user to do so without resorting to reflog.
> >   */
> > -void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit)
> > +void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
> > +			     int show_previous_position)
> >  {
> >  	struct rev_info revs;
> >  	struct object *object = &old_commit->object;
> > @@ -192,8 +193,10 @@ void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commi
> >  		die(_("internal error in revision walk"));
> >  	if (!(old_commit->object.flags & UNINTERESTING))
> >  		suggest_reattach(old_commit, &revs);
> > -	else
> > +	else if (show_previous_position)
> >  		describe_detached_head(_("Previous HEAD position was"), old_commit);
> > +	else
> > +		describe_detached_head(_("HEAD position was"), old_commit);
> 
> Can we think of a single way to phrase this batter?  It's not like

OK.

This is the current situation:

   $ git checkout --detach 
   HEAD is now at 2efe05c commit-a

   $ git checkout HEAD~1
   Previous HEAD position was 2efe05c commit-a
   HEAD is now at 7906992 commit-b

   $ git worktree add test --detach && git worktree remove test
   Preparing worktree (detached HEAD 7906992)
   HEAD is now at 7906992 commit-b

Maybe "HEAD position was" fits for both usages.  This is how it would
look like:

   $ git checkout -
   HEAD position was 7906992 commit-b
   HEAD is now at 2efe05c commit-a

   $ git worktree add test --detach && git worktree remove test
   Preparing worktree (detached HEAD 2efe05c)
   HEAD is now at 2efe05c commit-a
   HEAD position was 2efe05c commit-a

Or just "HEAD was at":

   $ git checkout -
   HEAD was at 2efe05c commit-a
   HEAD is now at 7906992 commit-b

   $ git worktree add test --detach && git worktree remove test
   Preparing worktree (detached HEAD 7906992)
   HEAD is now at 7906992 commit-b
   HEAD was at 7906992 commit-b

I think, if there are no objections or better suggestions, I'll re-roll
with "HEAD was at". 

> 
> > diff --git a/checkout.h b/checkout.h
> > index c7dc056544..ee400376d5 100644
> > --- a/checkout.h
> > +++ b/checkout.h
> > @@ -18,7 +18,8 @@ const char *unique_tracking_name(const char *name,
> >   * HEAD.  If it is not reachable from any ref, this is the last chance
> >   * for the user to do so without resorting to reflog.
> >   */
> > -void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit);
> > +void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
> > +			     int show_previous_position);
> >  
> >  void describe_detached_head(const char *msg, struct commit *commit);
> >  #endif /* CHECKOUT_H */
> > diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
> > index 230a55e99a..f2756f7137 100755
> > --- a/t/t2403-worktree-move.sh
> > +++ b/t/t2403-worktree-move.sh
> > @@ -247,4 +247,14 @@ test_expect_success 'not remove a repo with initialized submodule' '
> >  	)
> >  '
> >  
> > +test_expect_success 'warn when removing a worktree with orphan commits' '
> > +	git worktree add --detach foo &&
> > +	git -C foo commit -m one --allow-empty &&
> > +	git -C foo commit -m two --allow-empty &&
> > +	git worktree remove foo 2>err &&
> > +	test_i18ngrep "you are leaving 2 commits behind" err &&
> > +	test_i18ngrep ! "Previous HEAD position was" err
> > +	test_i18ngrep "HEAD position was" err
> > +'
> > +
> >  test_done

Thanks.
Junio C Hamano April 27, 2023, 5:46 a.m. UTC | #3
Rubén Justo <rjusto@gmail.com> writes:

> Maybe "HEAD position was" fits for both usages.  This is how it would
> look like:
> ...
> I think, if there are no objections or better suggestions, I'll re-roll
> with "HEAD was at". 

But does it convey the more important point?  The reason why "HEAD
WAS at" may matter is because the user is about to lose history
leading to it.  I wonder if we want to be more direct and alarming,
e.g.

    $ git checkout -
    About to lose history leading to 2efe05c commit-a
    HEAD is now at 7906992 commit-b

Whichever phrasing you end up using, I think the order of messages
should be made consistent between the two cases.  That is,

> Maybe "HEAD position was" fits for both usages.  This is how it would
> look like:
>
>    $ git checkout -
>    HEAD position was 7906992 commit-b
>    HEAD is now at 2efe05c commit-a

Here "git checkout" reports the lost HEAD and then the end result.

>    $ git worktree add test --detach && git worktree remove test
>    Preparing worktree (detached HEAD 2efe05c)
>    HEAD is now at 2efe05c commit-a
>    HEAD position was 2efe05c commit-a

But here "git worktree add" reports the end resultfirst and then
reports the lost HEAD.  It probably should report them in reverse.

Thanks.
Eric Sunshine April 27, 2023, 6:16 a.m. UTC | #4
On Thu, Apr 27, 2023 at 1:50 AM Junio C Hamano <gitster@pobox.com> wrote:
> Whichever phrasing you end up using, I think the order of messages
> should be made consistent between the two cases.  That is,
>
> > Maybe "HEAD position was" fits for both usages.  This is how it would
> > look like:
> >
> >    $ git checkout -
> >    HEAD position was 7906992 commit-b
> >    HEAD is now at 2efe05c commit-a
>
> Here "git checkout" reports the lost HEAD and then the end result.
>
> >    $ git worktree add test --detach && git worktree remove test
> >    Preparing worktree (detached HEAD 2efe05c)
> >    HEAD is now at 2efe05c commit-a
> >    HEAD position was 2efe05c commit-a
>
> But here "git worktree add" reports the end resultfirst and then
> reports the lost HEAD.  It probably should report them in reverse.

There may be a misunderstanding here due to the unfortunate
construction of Rubén's example which muddles together the output of
`git worktree add` and `git worktree remove`. For clarity, his example
should probably have been written:

  $ git worktree add test --detach
  Preparing worktree (detached HEAD 2efe05c)
  HEAD is now at 2efe05c commit-a
  $ git worktree remove test
  HEAD position was 2efe05c commit-a

although showing only the `git worktree remove` command would probably
have been even clearer.

Such example output does a good job of arguing in favor of your
suggestion to use phrasing which is more alarming:

  $ git checkout -
  Commit 2efe05c "commit-a" left dangling
  HEAD is now at 7906992 commit-b

  $ git worktree remove test
  Commit 2efe05c "commit-a" left dangling

(Hopefully someone can come up with better wording than "About to lose
history leading to" and "Commit ... left dangling", neither of which
sound quite right.)
Rubén Justo April 27, 2023, 11:08 p.m. UTC | #5
On 26-abr-2023 22:46:01, Junio C Hamano wrote:
> Rubén Justo <rjusto@gmail.com> writes:

The message: "Previous HEAD position was", which we have since dc9195ac78
(Let git-checkout always drop any detached head, 2007-02-03), describes
a detached HEAD that has been left behind.

In 8e2dc6ac06 (commit: give final warning when reattaching HEAD to leave
commits behind, 2011-02-18) we moved this message to a new function,
orphaned_commit_warning().  We still show the message if the HEAD left
behind is detached.  However, if the HEAD left behind is detached _and_
_not connected_ to any ref, instead of the original message, we show a
warning.

In this series, we want to use that function to show the same warning
when the user removes a worktree whose HEAD is detached and _not
connected_ to any ref.  However, if the HEAD is detached but connected,
the original message introduced in dc9195ac78 needs to be adjusted.

> > Maybe "HEAD position was" fits for both usages.  This is how it would
> > look like:
> > ...
> > I think, if there are no objections or better suggestions, I'll re-roll
> > with "HEAD was at". 

This is about the message introduced in dc9195ac78, but...

> But does it convey the more important point?  The reason why "HEAD

I think you are referring to the warning.

Starting from a situation like:

   $ git checkout -b foo
   Switched to a new branch 'foo'

   $ git checkout --detach
   HEAD is now at 47ab99a

   $ git commit --allow-empty -m dangling
   [detached HEAD 398a1b0] dangling

   $ git worktree add --detach foo-wt
   Preparing worktree (detached HEAD 398a1b0)
   HEAD is now at 398a1b0 dangling

If we switch to 'foo' in the current worktree, the message is:

   $ git checkout foo
   Warning: you are leaving 1 commit behind, not connected to
   any of your branches:
   
     398a1b0 dangling 
   
   If you want to keep it by creating a new branch, this may be a good time
   to do so with:
   
    git branch <new-branch-name> 398a1b0
   
   Switched to branch 'foo'

And -- this is what we are adding in this series -- the same message if
we remove the worktree 'foo-wt':

   $ git worktree remove foo-wt
   Warning: you are leaving 1 commit behind, not connected to
   any of your branches:
   
     398a1b0 dangling 
   
   If you want to keep it by creating a new branch, this may be a good time
   to do so with:
   
    git branch <new-branch-name> 398a1b0

> > Maybe "HEAD position was" fits for both usages.  This is how it would
> > look like:
> >
> >    $ git checkout -
> >    HEAD position was 7906992 commit-b
> >    HEAD is now at 2efe05c commit-a
> 
> Here "git checkout" reports the lost HEAD and then the end result.
> 
> >    $ git worktree add test --detach && git worktree remove test
> >    Preparing worktree (detached HEAD 2efe05c)
> >    HEAD is now at 2efe05c commit-a
> >    HEAD position was 2efe05c commit-a

I apologize, the examples were confusing.  I though it was a good idea
to show the new message next to other messages where we also refer to
the HEAD position.
Junio C Hamano April 28, 2023, 12:49 a.m. UTC | #6
Eric Sunshine <sunshine@sunshineco.com> writes:

> There may be a misunderstanding here due to the unfortunate
> construction of Rubén's example which muddles together the output of
> `git worktree add` and `git worktree remove`. For clarity, his example
> should probably have been written:
>
>   $ git worktree add test --detach
>   Preparing worktree (detached HEAD 2efe05c)
>   HEAD is now at 2efe05c commit-a
>   $ git worktree remove test
>   HEAD position was 2efe05c commit-a
>
> although showing only the `git worktree remove` command would probably
> have been even clearer.

Ah, you are absolutely right.

My "huh?" against the apparent inconsistency between "checkout" and
"worktree" regarding the order of "this is the end result" vs "this
is what we left behind" does not exist, as "worktree remove" does
not involve being newly on a detached HEAD and it is the one that
may introduce a newly abandoned line of history.  So everything
makes sense.

> Such example output does a good job of arguing in favor of your
> suggestion to use phrasing which is more alarming:
>
>   $ git checkout -
>   Commit 2efe05c "commit-a" left dangling
>   HEAD is now at 7906992 commit-b
>
>   $ git worktree remove test
>   Commit 2efe05c "commit-a" left dangling
>
> (Hopefully someone can come up with better wording than "About to lose
> history leading to" and "Commit ... left dangling", neither of which
> sound quite right.)

Yup, I am obviously worse at phrasing this than you are ;-) We'd
need a good wording that is alarming, even for those who squelch
most of the warning given via the advise system, without becoming
too verbose.

Thanks.
diff mbox series

Patch

diff --git a/builtin/checkout.c b/builtin/checkout.c
index 991413ef1a..85ac4bca00 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1051,7 +1051,7 @@  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);
+		orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit, 1);
 
 	update_refs_for_switch(opts, &old_branch_info, new_branch_info);
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a61bc32189..df269bccc8 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -1138,6 +1138,14 @@  static int remove_worktree(int ac, const char **av, const char *prefix)
 
 		ret |= delete_git_work_tree(wt);
 	}
+
+	if (!wt->head_ref && !is_null_oid(&wt->head_oid)) {
+		struct commit* wt_commit = lookup_commit_reference_gently(the_repository,
+									  &wt->head_oid, 1);
+		if (wt_commit)
+			orphaned_commit_warning(wt_commit, NULL, 0);
+	}
+
 	/*
 	 * continue on even if ret is non-zero, there's no going back
 	 * from here.
diff --git a/checkout.c b/checkout.c
index 18e7362043..5f7b0b3c49 100644
--- a/checkout.c
+++ b/checkout.c
@@ -171,7 +171,8 @@  static void suggest_reattach(struct commit *commit, struct rev_info *revs)
  * HEAD.  If it is not reachable from any ref, this is the last chance
  * for the user to do so without resorting to reflog.
  */
-void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit)
+void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
+			     int show_previous_position)
 {
 	struct rev_info revs;
 	struct object *object = &old_commit->object;
@@ -192,8 +193,10 @@  void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commi
 		die(_("internal error in revision walk"));
 	if (!(old_commit->object.flags & UNINTERESTING))
 		suggest_reattach(old_commit, &revs);
-	else
+	else if (show_previous_position)
 		describe_detached_head(_("Previous HEAD position was"), old_commit);
+	else
+		describe_detached_head(_("HEAD position was"), old_commit);
 
 	/* Clean up objects used, as they will be reused. */
 	repo_clear_commit_marks(the_repository, ALL_REV_FLAGS);
diff --git a/checkout.h b/checkout.h
index c7dc056544..ee400376d5 100644
--- a/checkout.h
+++ b/checkout.h
@@ -18,7 +18,8 @@  const char *unique_tracking_name(const char *name,
  * HEAD.  If it is not reachable from any ref, this is the last chance
  * for the user to do so without resorting to reflog.
  */
-void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit);
+void orphaned_commit_warning(struct commit *old_commit, struct commit *new_commit,
+			     int show_previous_position);
 
 void describe_detached_head(const char *msg, struct commit *commit);
 #endif /* CHECKOUT_H */
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 230a55e99a..f2756f7137 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -247,4 +247,14 @@  test_expect_success 'not remove a repo with initialized submodule' '
 	)
 '
 
+test_expect_success 'warn when removing a worktree with orphan commits' '
+	git worktree add --detach foo &&
+	git -C foo commit -m one --allow-empty &&
+	git -C foo commit -m two --allow-empty &&
+	git worktree remove foo 2>err &&
+	test_i18ngrep "you are leaving 2 commits behind" err &&
+	test_i18ngrep ! "Previous HEAD position was" err
+	test_i18ngrep "HEAD position was" err
+'
+
 test_done