Skip to content

Conversation

@derrickstolee
Copy link

@derrickstolee derrickstolee commented Nov 7, 2025

The postCommand.strategy=worktree-change config option allows for skipping the post-command hook when the Git command doesn't change the worktree. However, sometimes this config isn't loaded before the post-command hook is invoked, causing the hook to run in cases where we'd prefer it to not run.

Examples include: git version or git <typo>.

The tricky bit is that there are several places where we get here and standard config mechanisms can't load due to not having a gitdir in the repository struct. We fix this by:

  1. Using the "load early config" mechanism also used by core.hooksPath.
  2. Skipping the lookup for the sentinel file when there isn't a gitdir since we couldn't have written one without it.

The change is given in two commits: the first expands the tests with the existing behavior and the second changes the behavior, showing the impact on those tests.

  • This change only applies to microsoft/git specifics (post-command hook)

See #736 and #748 for prior art in this space.

@derrickstolee derrickstolee self-assigned this Nov 7, 2025
@mjcheetham
Copy link
Member

So with this change, if you have only a post-command hook, and no pre-command hook, then if there's no gitdir it will not run (irrespective of the strategy since we haven't any config to read).

I guess this makes sense. It does mean that if you wanted a hook to, say, aid with typos or commands outside of a repo, then you couldn't use a post-command hook for that anymore. Is this a reasonable restriction?

@derrickstolee
Copy link
Author

So with this change, if you have only a post-command hook, and no pre-command hook, then if there's no gitdir it will not run (irrespective of the strategy since we haven't any config to read).

If r->gitdir is NULL, then it's that the repository hasn't been loaded yet. This is due to the builtins using custom settings for how it prepares the repo.

But as I write this reply, I realize that aliases or dash-commands may have some special behavior that must be accounted for. I'm investigating now.

I guess this makes sense. It does mean that if you wanted a hook to, say, aid with typos or commands outside of a repo, then you couldn't use a post-command hook for that anymore. Is this a reasonable restriction?

The post-command hook only works within a $GITDIR which is somehow set up even when the config hasn't been loaded. It would never run if the Git command is run without a $GITDIR variable or its current directory living within a repo on disk. That won't happen in any case.

dscho
dscho previously approved these changes Nov 7, 2025
Copy link
Member

@dscho dscho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

@derrickstolee
Copy link
Author

Thanks for the quick looks, but the alias scenario showed me some interesting complications. I'm pretty happy with the solution now, so please re-review! There are two commits now, where the first is only adding tests to demonstrate untested behavior. The second makes the behavior change and demonstrates how that impacts the new tests.

Copy link
Member

@dscho dscho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the structure and presentation of this PR!

When the top-level Git process is an alias, it doesn't load much config
and thus the postCommand.strategy config setting is not loaded properly.
This leads to the post-command hook being run more frequently than we want,
including an alias for 'git add' running the hook even when the worktree
did not change.

Similar state is not loaded by 'git version' or 'git typo'.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Copy link

@emilyyang-ms emilyyang-ms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 2 commits demonstrated the PR really well, thanks!

Copy link
Member

@wilbaker wilbaker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for handling the alias case as well!

I left one small question about formatting, thanks!

When the post-command hook runs, we could be in several custom states:

1. The command is a regular builtin, but the repo setup is incomplete.
   (Example: "git version", "git rev-parse HEAD")

2. The command is a dashed command, but the top level process uses a space
   in its call. (Example: "git http-request")

3. The command is an alias that goes to a builtin.

4. The command has no arguments or is only helptext.

Each of these cases leads to a place where we previously had not loaded the
postCommand.strategy config and would execute the post-command hook without
looking for a sentinel file.

There are two fixes here:

First, we use early config parsing so we can get config details without
fully setting up the repository. This is how core.hooksPath works in these
situations.

Second, we need to make sure handle_hook_replacement() is called even when
the repo's gitdir is NULL. This requires a small amount of care to say that
a sentinel file cannot exist if the gitdir isn't set, as we would not have
written one without initializing the gitdir.

This gets all of the desired behaviors we want for this setting without
doing anything extreme with how pre- and post-command hooks run otherwise.

Signed-off-by: Derrick Stolee <stolee@gmail.com>
Copy link
Member

@dscho dscho left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

@derrickstolee derrickstolee merged commit c926252 into microsoft:vfs-2.51.2 Nov 10, 2025
64 of 65 checks passed
dscho pushed a commit that referenced this pull request Nov 10, 2025
The postCommand.strategy=worktree-change config option allows for
skipping the post-command hook when the Git command doesn't change the
worktree. However, sometimes this config isn't loaded before the
post-command hook is invoked, causing the hook to run in cases where
we'd prefer it to not run.

Examples include: `git version` or `git <typo>`.

The tricky bit is that there are several places where we get here and
standard config mechanisms can't load due to not having a `gitdir` in
the repository struct. We fix this by:

1. Using the "load early config" mechanism also used by
`core.hooksPath`.
2. Skipping the lookup for the sentinel file when there isn't a `gitdir`
since we couldn't have written one without it.

The change is given in two commits: the first expands the tests with the
existing behavior and the second changes the behavior, showing the
impact on those tests.

* [X] This change only applies to microsoft/git specifics (post-command
hook)

See #736 and #748 for prior art in this space.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants