added hooks corresponding to annex.*-command

* Added freezecontent-annex and thawcontent-annex hooks that
  correspond to the git configs annex.freezecontent and
  annex.thawcontent.
* Added secure-erase-annex hook that corresponds to the git config
  annex.secure-erase-command.
* Added commitmessage-annex hook that corresponds to the git config
  annex.commitmessage-command.
* Added http-headers-annex hook that corresponds to the git config
  annex.http-headers-command.
  that correspond to the post-update-annex and pre-commit-annex hooks.

The use case for these is eg, setting up a git repository that is run in a
container, where the easiest way to provide a script is by putting it in
.git/hooks/, rather than copying it into the container in a way that puts
it in PATH.

This is all the ones that make sense to add for annex.*-config git configs.
annex.youtube-dl-command is not a hook, it's telling git-annex what command
to run. So is annex.shared-sop-command. So omitted those.

May later also want to add hooks corresponding to
`remote.<name>.annex-cost-command` etc.

Sponsored-by: the NIH-funded NICEMAN (ReproNim TR&D3) project
This commit is contained in:
Joey Hess 2025-01-10 14:50:49 -04:00
parent 5df1b2b36e
commit a73fa77417
No known key found for this signature in database
GPG key ID: DB12DB0FF05F8F38
11 changed files with 131 additions and 53 deletions

View file

@ -521,12 +521,10 @@ createMessage :: Annex String
createMessage = fromMaybe "branch created" <$> getCommitMessage createMessage = fromMaybe "branch created" <$> getCommitMessage
getCommitMessage :: Annex (Maybe String) getCommitMessage :: Annex (Maybe String)
getCommitMessage = do getCommitMessage =
config <- Annex.getGitConfig outputOfAnnexHook commitMessageAnnexHook annexCommitMessageCommand
case annexCommitMessageCommand config of <|>
Nothing -> return (annexCommitMessage config) (annexCommitMessage <$> Annex.getGitConfig)
Just cmd -> catchDefaultIO (annexCommitMessage config) $
Just <$> liftIO (readProcess "sh" ["-c", cmd])
{- Stages the journal, and commits staged changes to the branch. -} {- Stages the journal, and commits staged changes to the branch. -}
commit :: String -> Annex () commit :: String -> Annex ()

View file

@ -10,6 +10,7 @@
module Annex.Content.LowLevel where module Annex.Content.LowLevel where
import Annex.Common import Annex.Common
import Annex.Hook
import Logs.Transfer import Logs.Transfer
import qualified Annex import qualified Annex
import Utility.DiskFree import Utility.DiskFree
@ -25,11 +26,8 @@ import System.PosixCompat.Files (linkCount)
- File may or may not be deleted at the end; caller is responsible for - File may or may not be deleted at the end; caller is responsible for
- making sure it's deleted. -} - making sure it's deleted. -}
secureErase :: RawFilePath -> Annex () secureErase :: RawFilePath -> Annex ()
secureErase file = maybe noop go =<< annexSecureEraseCommand <$> Annex.getGitConfig secureErase = void . runAnnexPathHook "%file"
where secureEraseAnnexHook annexSecureEraseCommand
go basecmd = void $ liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
gencmd = massReplace [ ("%file", shellEscape (fromRawFilePath file)) ]
data LinkedOrCopied = Linked | Copied data LinkedOrCopied = Linked | Copied

View file

@ -4,7 +4,7 @@
- git-annex not change, otherwise removing old hooks using an old - git-annex not change, otherwise removing old hooks using an old
- version of the script would fail. - version of the script would fail.
- -
- Copyright 2013-2019 Joey Hess <id@joeyh.name> - Copyright 2013-2025 Joey Hess <id@joeyh.name>
- -
- Licensed under the GNU AGPL version 3 or higher. - Licensed under the GNU AGPL version 3 or higher.
-} -}
@ -48,6 +48,21 @@ preCommitAnnexHook = Git.Hook "pre-commit-annex" "" []
postUpdateAnnexHook :: Git.Hook postUpdateAnnexHook :: Git.Hook
postUpdateAnnexHook = Git.Hook "post-update-annex" "" [] postUpdateAnnexHook = Git.Hook "post-update-annex" "" []
freezeContentAnnexHook :: Git.Hook
freezeContentAnnexHook = Git.Hook "freezecontent-annex" "" []
thawContentAnnexHook :: Git.Hook
thawContentAnnexHook = Git.Hook "thawcontent-annex" "" []
secureEraseAnnexHook :: Git.Hook
secureEraseAnnexHook = Git.Hook "secure-erase-annex" "" []
commitMessageAnnexHook :: Git.Hook
commitMessageAnnexHook = Git.Hook "commitmessage-annex" "" []
httpHeadersAnnexHook :: Git.Hook
httpHeadersAnnexHook = Git.Hook "http-headers-annex" "" []
mkHookScript :: String -> String mkHookScript :: String -> String
mkHookScript s = unlines mkHookScript s = unlines
[ shebang [ shebang
@ -69,23 +84,26 @@ hookWarning h msg = do
warning $ UnquotedString $ warning $ UnquotedString $
Git.hookName h ++ " hook (" ++ Git.hookFile h r ++ ") " ++ msg Git.hookName h ++ " hook (" ++ Git.hookFile h r ++ ") " ++ msg
{- Runs a hook. To avoid checking if the hook exists every time, {- To avoid checking if the hook exists every time, the existing hooks
- the existing hooks are cached. -} - are cached. -}
runAnnexHook :: Git.Hook -> (GitConfig -> Maybe String) -> Annex () doesAnnexHookExist :: Git.Hook -> Annex Bool
runAnnexHook hook commandcfg = do doesAnnexHookExist hook = do
m <- Annex.getState Annex.existinghooks m <- Annex.getState Annex.existinghooks
case M.lookup hook m of case M.lookup hook m of
Just True -> runhook Just exists -> return exists
Just False -> runcommandcfg
Nothing -> do Nothing -> do
exists <- inRepo $ Git.hookExists hook exists <- inRepo $ Git.hookExists hook
Annex.changeState $ \s -> s Annex.changeState $ \s -> s
{ Annex.existinghooks = M.insert hook exists m } { Annex.existinghooks = M.insert hook exists m }
if exists return exists
then runhook
else runcommandcfg runAnnexHook :: Git.Hook -> (GitConfig -> Maybe String) -> Annex ()
runAnnexHook hook commandcfg = ifM (doesAnnexHookExist hook)
( runhook
, runcommandcfg
)
where where
runhook = unlessM (inRepo $ Git.runHook hook) $ do runhook = unlessM (inRepo $ Git.runHook boolSystem hook []) $ do
h <- fromRepo $ Git.hookFile hook h <- fromRepo $ Git.hookFile hook
commandfailed h commandfailed h
runcommandcfg = commandcfg <$> Annex.getGitConfig >>= \case runcommandcfg = commandcfg <$> Annex.getGitConfig >>= \case
@ -94,3 +112,29 @@ runAnnexHook hook commandcfg = do
commandfailed command commandfailed command
Nothing -> noop Nothing -> noop
commandfailed c = warning $ UnquotedString $ c ++ " failed" commandfailed c = warning $ UnquotedString $ c ++ " failed"
runAnnexPathHook :: String -> Git.Hook -> (GitConfig -> Maybe String) -> RawFilePath -> Annex Bool
runAnnexPathHook pathtoken hook commandcfg p = ifM (doesAnnexHookExist hook)
( runhook
, runcommandcfg
)
where
runhook = inRepo $ Git.runHook boolSystem hook [ File (fromRawFilePath p) ]
runcommandcfg = commandcfg <$> Annex.getGitConfig >>= \case
Just basecmd -> liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
Nothing -> return True
gencmd = massReplace [ (pathtoken, shellEscape (fromRawFilePath p)) ]
outputOfAnnexHook :: Git.Hook -> (GitConfig -> Maybe String) -> Annex (Maybe String)
outputOfAnnexHook hook commandcfg = ifM (doesAnnexHookExist hook)
( runhook
, runcommandcfg
)
where
runhook = inRepo (Git.runHook runhook' hook [])
runhook' c ps = Just <$> readProcess c (toCommand ps)
runcommandcfg = commandcfg <$> Annex.getGitConfig >>= \case
Just command -> liftIO $
Just <$> readProcess "sh" ["-c", command]
Nothing -> return Nothing

View file

@ -33,6 +33,7 @@ module Annex.Perms (
) where ) where
import Annex.Common import Annex.Common
import Annex.Hook
import Utility.FileMode import Utility.FileMode
import Git import Git
import Git.ConfigTypes import Git.ConfigTypes
@ -340,24 +341,24 @@ modifyContentDirWhenExists f a = do
either throwM return v either throwM return v
hasFreezeHook :: Annex Bool hasFreezeHook :: Annex Bool
hasFreezeHook = isJust . annexFreezeContentCommand <$> Annex.getGitConfig hasFreezeHook =
(isJust . annexFreezeContentCommand <$> Annex.getGitConfig)
<||>
(doesAnnexHookExist freezeContentAnnexHook)
hasThawHook :: Annex Bool hasThawHook :: Annex Bool
hasThawHook = isJust . annexThawContentCommand <$> Annex.getGitConfig hasThawHook =
(isJust . annexThawContentCommand <$> Annex.getGitConfig)
<||>
(doesAnnexHookExist thawContentAnnexHook)
freezeHook :: RawFilePath -> Annex () freezeHook :: RawFilePath -> Annex ()
freezeHook p = maybe noop go =<< annexFreezeContentCommand <$> Annex.getGitConfig freezeHook = void . runAnnexPathHook "%path"
where freezeContentAnnexHook annexFreezeContentCommand
go basecmd = void $ liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ]
thawHook :: RawFilePath -> Annex () thawHook :: RawFilePath -> Annex ()
thawHook p = maybe noop go =<< annexThawContentCommand <$> Annex.getGitConfig thawHook = void . runAnnexPathHook "%path"
where thawContentAnnexHook annexThawContentCommand
go basecmd = void $ liftIO $
boolSystem "sh" [Param "-c", Param $ gencmd basecmd]
gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ]
{- Calculate mode to use for a directory from the mode to use for a file. {- Calculate mode to use for a directory from the mode to use for a file.
- -

View file

@ -35,6 +35,7 @@ import Annex.Common
import qualified Annex import qualified Annex
import qualified Utility.Url as U import qualified Utility.Url as U
import qualified Utility.Url.Parse as U import qualified Utility.Url.Parse as U
import Annex.Hook
import Utility.Hash (IncrementalVerifier) import Utility.Hash (IncrementalVerifier)
import Utility.IPAddress import Utility.IPAddress
import Network.HTTP.Client.Restricted import Network.HTTP.Client.Restricted
@ -75,9 +76,11 @@ getUrlOptions = Annex.getState Annex.urloptions >>= \case
<*> pure (Just (\u -> "Configuration of annex.security.allowed-url-schemes does not allow accessing " ++ show u)) <*> pure (Just (\u -> "Configuration of annex.security.allowed-url-schemes does not allow accessing " ++ show u))
<*> pure U.noBasicAuth <*> pure U.noBasicAuth
headers = annexHttpHeadersCommand <$> Annex.getGitConfig >>= \case headers =
Just cmd -> lines <$> liftIO (readProcess "sh" ["-c", cmd]) outputOfAnnexHook httpHeadersAnnexHook annexHttpHeadersCommand
Nothing -> annexHttpHeaders <$> Annex.getGitConfig >>= \case
Just output -> pure (lines output)
Nothing -> annexHttpHeaders <$> Annex.getGitConfig
checkallowedaddr = words . annexAllowedIPAddresses <$> Annex.getGitConfig >>= \case checkallowedaddr = words . annexAllowedIPAddresses <$> Annex.getGitConfig >>= \case
["all"] -> do ["all"] -> do

View file

@ -11,9 +11,17 @@ git-annex (10.20250103) UNRELEASED; urgency=medium
* git-remote-annex: Use enableremote rather than initremote. * git-remote-annex: Use enableremote rather than initremote.
* Windows: Fix permission denied error when dropping files that * Windows: Fix permission denied error when dropping files that
have the readonly attribute set. have the readonly attribute set.
* Added freezecontent-annex and thawcontent-annex hooks that
correspond to the git configs annex.freezecontent and
annex.thawcontent.
* Added secure-erase-annex hook that corresponds to the git config
annex.secure-erase-command.
* Added commitmessage-annex hook that corresponds to the git config
annex.commitmessage-command.
* Added http-headers-annex hook that corresponds to the git config
annex.http-headers-command.
* Added git configs annex.post-update-command and annex.pre-commit-command * Added git configs annex.post-update-command and annex.pre-commit-command
that correspond to the git-annex hook scripts post-update-annex and that correspond to the post-update-annex and pre-commit-annex hooks.
pre-commit-annex.
-- Joey Hess <id@joeyh.name> Fri, 03 Jan 2025 14:30:38 -0400 -- Joey Hess <id@joeyh.name> Fri, 03 Jan 2025 14:30:38 -0400

View file

@ -108,8 +108,8 @@ hookExists h r = do
doesFileExist f doesFileExist f
#endif #endif
runHook :: Hook -> Repo -> IO Bool runHook :: (FilePath -> [CommandParam] -> IO a) -> Hook -> [CommandParam] -> Repo -> IO a
runHook h r = do runHook runner h ps r = do
let f = hookFile h r let f = hookFile h r
(c, ps) <- findShellCommand f (c, cps) <- findShellCommand f
boolSystem c ps runner c (cps ++ ps)

View file

@ -1154,17 +1154,20 @@ repository, using [[git-annex-config]]. See its man page for a list.)
This command is run and its output is used as the commit message to the This command is run and its output is used as the commit message to the
git-annex branch. git-annex branch.
Alternatively, a hook script can be installed in
`.git/hooks/commitmessage-annex`
* `annex.post-update-command` * `annex.post-update-command`
This command is run after git-annex updates the git-annex branch. This command is run after git-annex updates the git-annex branch.
Alternatively, a hook script can be installed in
`.git/hooks/post-update-annex`
When publishing a git-annex repository by http, this can be used to run When publishing a git-annex repository by http, this can be used to run
`git update-server-info` `git update-server-info`
Alternatively, a hook script can be installed in
`.git/hooks/post-update-annex`
* `annex.pre-commit-command` * `annex.pre-commit-command`
This command is run whenever a commit is made to the HEAD branch of This command is run whenever a commit is made to the HEAD branch of
@ -1422,21 +1425,28 @@ repository, using [[git-annex-config]]. See its man page for a list.)
erased. erased.
For example, to use the wipe command, set it to `wipe -f %file`. For example, to use the wipe command, set it to `wipe -f %file`.
Alternatively to setting the git config, a hook script can be installed
in `.git/hooks/secure-erase-annex`
* `annex.freezecontent-command`, `annex.thawcontent-command` * `annex.freezecontent-command`, `annex.thawcontent-command`
Usually the write permission bits are unset to protect annexed objects Usually the write permission bits are unset to protect annexed objects
from being modified or deleted. The freezecontent-command is run after from being modified or deleted. Freezecontent is run after
git-annex has removed (or attempted to remove) the write bit, and can git-annex has removed (or attempted to remove) the write bit, and can
be used to prevent writing in some other way. be used to prevent writing in some other way.
The thawcontent-command should undo its effect, and is run before Tawcontent should undo its effect, and is run before
git-annex restores the write bit. git-annex restores the write bit.
In the command line, %path is replaced with the file or directory to In the command line, %path is replaced with the file or directory to
operate on. operate on.
(When annex.crippledfilesystem is set, git-annex will not try to (When annex.crippledfilesystem is set, git-annex will not try to
remove/restore the write bit, but it will still run these hooks.) remove/restore the write bit, but it will still run freezecontent and
thawcontent.)
Alternatively to setting the git config, hook scripts can be installed
in `.git/hooks/freezecontent-annex` and `.git/hooks/thawcontent-annex`.
* `annex.tune.objecthash1`, `annex.tune.objecthashlower`, `annex.tune.branchhash1` * `annex.tune.objecthash1`, `annex.tune.objecthashlower`, `annex.tune.branchhash1`
@ -2065,6 +2075,9 @@ Remotes are configured using these settings in `.git/config`.
If set, the command is run and each line of its output is used as a HTTP If set, the command is run and each line of its output is used as a HTTP
header. This overrides annex.http-headers. header. This overrides annex.http-headers.
Alternatively, a hook script can be installed in
`.git/hooks/http-headers-annex`
* `annex.security.allowed-url-schemes` * `annex.security.allowed-url-schemes`

View file

@ -25,3 +25,5 @@ I wonder if there could be a way added to be able to specify them relative to th
[[!meta author=yoh]] [[!meta author=yoh]]
[[!tag projects/repronim]] [[!tag projects/repronim]]
> [[done]] --[[Joey]]

View file

@ -23,12 +23,11 @@ hook that git-annex writes. But, it seems worth having the git config just
for consistency. for consistency.
There are some things like annex.youtube-dl-command and There are some things like annex.youtube-dl-command and
annex.http-headers-command that are configuring commands for git-annex to annex.shared-sop-command that are configuring commands for git-annex to
run, and are not really hooks per se. run, and are not really hooks per se.
And it does not make sense to have hook scripts that a specific to a given And it does not make sense to have hook scripts that a specific to a given
remote corresponding to configs like `remote.name.annex-cost-command`. remote corresponding to configs like `remote.name.annex-cost-command`.
Instead it might make sense to have a `.git/hooks/remote-cost-annex` that Instead there could be a single `.git/hooks/remote-cost-annex` that
is passed the name of the remote, but that bridge can be crossed if we is passed the name of the remote.
come to it.
"""]] """]]

View file

@ -0,0 +1,12 @@
[[!comment format=mdwn
username="joey"
subject="""comment 7"""
date="2025-01-10T18:50:07Z"
content="""
Implemented hooks: freezecontent-annex, thawcontent-annex,
secure-erase-annex, commitmessage-annex, http-headers-annex
That leaves only `remote.name.annex-cost-command` and similar git configs
that don't have hooks. And a few like annex.youtube-dl-command that are not
really equivilant to hooks.
"""]]