Backing up a hook without hard links makes it executable (0755), so a hook switched off with chmod -x runs again

Open Beginner friendly
#3,049 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
76/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
git, go
Domain
cli, devtools

Research direction

Start with prepareHookBackup, replaceWithCopy, and copyHookFile, following the copy fallback described in the issue. Check the existing mode-preserving helper and link-failure test seam from #3046 for the related re-wrap path. Add or update a unit test that forces the link to fail; done means the backup preserves the source permissions, including non-executable and private modes.

Written by the indexing model from the issue text.

Description

Description

When hard links are unavailable in the hooks directory, entire enable copies the user's hook into <hook>.pre-entire, and the copy is always mode 0755. Entire's chained hook runs <hook>.pre-entire when it is executable, so a hook the user switched off with chmod -x (which git skips) starts running on every commit or push. A private hook (0600) also becomes world-readable.

Trace on main (3a760b96): prepareHookBackup calls replaceWithCopy(root, name, backup), which calls linkOrCopy. When root.Link fails with anything other than fs.ErrExist, copyOrRelink falls back to copyHookFile, which opens the copy with 0o755 and then chmods it to 0o755. keepOlderCopy and replaceWithOwnCopy go through the same copy, so older copies and keep copies also end up 0755. A hard link shares the source's mode, so filesystems that support links are not affected.

Proposed fix: have copyHookFile (or its callers that place a hook where it runs) give the copy the source's permission bits. #3046 already has a helper for this on the re-wrap path (replaceWithCopyKeepingMode) and a test seam for the link (linkHookFile).

Repro

  1. Put a repo's hooks on a filesystem without hard links (some FUSE or network mounts, exFAT), or in a unit test, make the hard link fail.
  2. printf '#!/bin/sh\necho user hook ran\n' > .git/hooks/pre-push; chmod 644 .git/hooks/pre-push. Git now skips it.
  3. entire enable.
  4. ls -l .git/hooks/pre-push.pre-entire shows -rwxr-xr-x, and git push prints "user hook ran".

Expected: pre-push.pre-entire keeps mode 0644, and the push does not run it.

Not reproduced end to end on a filesystem without links. It is traced in source, and reproduced in a unit test by making the link fail.

Version

main 3a760b96

Agent

Any (the git hook install is the same for every agent)

Additional

Found while fixing #2960 in #3046, where the same copy fallback is fixed for the re-wrap path only.

Dominant language
Go
Stars
5.1k
Forks
475
Avg merge
1d 14h
Merged PRs (30d)
208

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from entireio/cli

All issues in entireio/cli

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.