diff --git a/Annex/Branch.hs b/Annex/Branch.hs index 03adce0886..ce4c3ad85e 100644 --- a/Annex/Branch.hs +++ b/Annex/Branch.hs @@ -521,12 +521,10 @@ createMessage :: Annex String createMessage = fromMaybe "branch created" <$> getCommitMessage getCommitMessage :: Annex (Maybe String) -getCommitMessage = do - config <- Annex.getGitConfig - case annexCommitMessageCommand config of - Nothing -> return (annexCommitMessage config) - Just cmd -> catchDefaultIO (annexCommitMessage config) $ - Just <$> liftIO (readProcess "sh" ["-c", cmd]) +getCommitMessage = + outputOfAnnexHook commitMessageAnnexHook annexCommitMessageCommand + <|> + (annexCommitMessage <$> Annex.getGitConfig) {- Stages the journal, and commits staged changes to the branch. -} commit :: String -> Annex () diff --git a/Annex/Content/LowLevel.hs b/Annex/Content/LowLevel.hs index 9d732f6a6e..69baf19957 100644 --- a/Annex/Content/LowLevel.hs +++ b/Annex/Content/LowLevel.hs @@ -10,6 +10,7 @@ module Annex.Content.LowLevel where import Annex.Common +import Annex.Hook import Logs.Transfer import qualified Annex 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 - making sure it's deleted. -} secureErase :: RawFilePath -> Annex () -secureErase file = maybe noop go =<< annexSecureEraseCommand <$> Annex.getGitConfig - where - go basecmd = void $ liftIO $ - boolSystem "sh" [Param "-c", Param $ gencmd basecmd] - gencmd = massReplace [ ("%file", shellEscape (fromRawFilePath file)) ] +secureErase = void . runAnnexPathHook "%file" + secureEraseAnnexHook annexSecureEraseCommand data LinkedOrCopied = Linked | Copied diff --git a/Annex/Hook.hs b/Annex/Hook.hs index deadf871ed..e4264ce9d5 100644 --- a/Annex/Hook.hs +++ b/Annex/Hook.hs @@ -4,7 +4,7 @@ - git-annex not change, otherwise removing old hooks using an old - version of the script would fail. - - - Copyright 2013-2019 Joey Hess + - Copyright 2013-2025 Joey Hess - - 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 "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 s = unlines [ shebang @@ -69,23 +84,26 @@ hookWarning h msg = do warning $ UnquotedString $ Git.hookName h ++ " hook (" ++ Git.hookFile h r ++ ") " ++ msg -{- Runs a hook. To avoid checking if the hook exists every time, - - the existing hooks are cached. -} -runAnnexHook :: Git.Hook -> (GitConfig -> Maybe String) -> Annex () -runAnnexHook hook commandcfg = do +{- To avoid checking if the hook exists every time, the existing hooks + - are cached. -} +doesAnnexHookExist :: Git.Hook -> Annex Bool +doesAnnexHookExist hook = do m <- Annex.getState Annex.existinghooks case M.lookup hook m of - Just True -> runhook - Just False -> runcommandcfg + Just exists -> return exists Nothing -> do exists <- inRepo $ Git.hookExists hook Annex.changeState $ \s -> s { Annex.existinghooks = M.insert hook exists m } - if exists - then runhook - else runcommandcfg + return exists + +runAnnexHook :: Git.Hook -> (GitConfig -> Maybe String) -> Annex () +runAnnexHook hook commandcfg = ifM (doesAnnexHookExist hook) + ( runhook + , runcommandcfg + ) where - runhook = unlessM (inRepo $ Git.runHook hook) $ do + runhook = unlessM (inRepo $ Git.runHook boolSystem hook []) $ do h <- fromRepo $ Git.hookFile hook commandfailed h runcommandcfg = commandcfg <$> Annex.getGitConfig >>= \case @@ -94,3 +112,29 @@ runAnnexHook hook commandcfg = do commandfailed command Nothing -> noop 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 diff --git a/Annex/Perms.hs b/Annex/Perms.hs index 56bd35190e..03bce4fe83 100644 --- a/Annex/Perms.hs +++ b/Annex/Perms.hs @@ -33,6 +33,7 @@ module Annex.Perms ( ) where import Annex.Common +import Annex.Hook import Utility.FileMode import Git import Git.ConfigTypes @@ -340,24 +341,24 @@ modifyContentDirWhenExists f a = do either throwM return v hasFreezeHook :: Annex Bool -hasFreezeHook = isJust . annexFreezeContentCommand <$> Annex.getGitConfig +hasFreezeHook = + (isJust . annexFreezeContentCommand <$> Annex.getGitConfig) + <||> + (doesAnnexHookExist freezeContentAnnexHook) hasThawHook :: Annex Bool -hasThawHook = isJust . annexThawContentCommand <$> Annex.getGitConfig +hasThawHook = + (isJust . annexThawContentCommand <$> Annex.getGitConfig) + <||> + (doesAnnexHookExist thawContentAnnexHook) freezeHook :: RawFilePath -> Annex () -freezeHook p = maybe noop go =<< annexFreezeContentCommand <$> Annex.getGitConfig - where - go basecmd = void $ liftIO $ - boolSystem "sh" [Param "-c", Param $ gencmd basecmd] - gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ] +freezeHook = void . runAnnexPathHook "%path" + freezeContentAnnexHook annexFreezeContentCommand thawHook :: RawFilePath -> Annex () -thawHook p = maybe noop go =<< annexThawContentCommand <$> Annex.getGitConfig - where - go basecmd = void $ liftIO $ - boolSystem "sh" [Param "-c", Param $ gencmd basecmd] - gencmd = massReplace [ ("%path", shellEscape (fromRawFilePath p)) ] +thawHook = void . runAnnexPathHook "%path" + thawContentAnnexHook annexThawContentCommand {- Calculate mode to use for a directory from the mode to use for a file. - diff --git a/Annex/Url.hs b/Annex/Url.hs index 2f12a10768..e796b314b9 100644 --- a/Annex/Url.hs +++ b/Annex/Url.hs @@ -35,6 +35,7 @@ import Annex.Common import qualified Annex import qualified Utility.Url as U import qualified Utility.Url.Parse as U +import Annex.Hook import Utility.Hash (IncrementalVerifier) import Utility.IPAddress 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 U.noBasicAuth - headers = annexHttpHeadersCommand <$> Annex.getGitConfig >>= \case - Just cmd -> lines <$> liftIO (readProcess "sh" ["-c", cmd]) - Nothing -> annexHttpHeaders <$> Annex.getGitConfig + headers = + outputOfAnnexHook httpHeadersAnnexHook annexHttpHeadersCommand + >>= \case + Just output -> pure (lines output) + Nothing -> annexHttpHeaders <$> Annex.getGitConfig checkallowedaddr = words . annexAllowedIPAddresses <$> Annex.getGitConfig >>= \case ["all"] -> do diff --git a/CHANGELOG b/CHANGELOG index 42bcfa6fb1..101fb7d0e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,9 +11,17 @@ git-annex (10.20250103) UNRELEASED; urgency=medium * git-remote-annex: Use enableremote rather than initremote. * Windows: Fix permission denied error when dropping files that 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 - that correspond to the git-annex hook scripts post-update-annex and - pre-commit-annex. + that correspond to the post-update-annex and pre-commit-annex hooks. -- Joey Hess Fri, 03 Jan 2025 14:30:38 -0400 diff --git a/Git/Hook.hs b/Git/Hook.hs index 1301ec2180..1163f1effe 100644 --- a/Git/Hook.hs +++ b/Git/Hook.hs @@ -108,8 +108,8 @@ hookExists h r = do doesFileExist f #endif -runHook :: Hook -> Repo -> IO Bool -runHook h r = do +runHook :: (FilePath -> [CommandParam] -> IO a) -> Hook -> [CommandParam] -> Repo -> IO a +runHook runner h ps r = do let f = hookFile h r - (c, ps) <- findShellCommand f - boolSystem c ps + (c, cps) <- findShellCommand f + runner c (cps ++ ps) diff --git a/doc/git-annex.mdwn b/doc/git-annex.mdwn index 1f499cb315..81b6dc1727 100644 --- a/doc/git-annex.mdwn +++ b/doc/git-annex.mdwn @@ -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 git-annex branch. + + Alternatively, a hook script can be installed in + `.git/hooks/commitmessage-annex` * `annex.post-update-command` 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 `git update-server-info` + Alternatively, a hook script can be installed in + `.git/hooks/post-update-annex` + * `annex.pre-commit-command` 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. 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` 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 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. In the command line, %path is replaced with the file or directory to operate on. (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` @@ -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 header. This overrides annex.http-headers. + + Alternatively, a hook script can be installed in + `.git/hooks/http-headers-annex` * `annex.security.allowed-url-schemes` diff --git a/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir.mdwn b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir.mdwn index f6a51fb30f..899b8a6994 100644 --- a/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir.mdwn +++ b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir.mdwn @@ -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]] [[!tag projects/repronim]] + +> [[done]] --[[Joey]] diff --git a/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_6_6bda77480362b38d6984d38430a1dc17._comment b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_6_6bda77480362b38d6984d38430a1dc17._comment index 5b5c5d12fe..8bb1abd1dd 100644 --- a/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_6_6bda77480362b38d6984d38430a1dc17._comment +++ b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_6_6bda77480362b38d6984d38430a1dc17._comment @@ -23,12 +23,11 @@ hook that git-annex writes. But, it seems worth having the git config just for consistency. 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. 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`. -Instead it might make sense to have a `.git/hooks/remote-cost-annex` that -is passed the name of the remote, but that bridge can be crossed if we -come to it. +Instead there could be a single `.git/hooks/remote-cost-annex` that +is passed the name of the remote. """]] diff --git a/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_7_8fd25d5d2b802c6ff6ee0a3802d6f1cb._comment b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_7_8fd25d5d2b802c6ff6ee0a3802d6f1cb._comment new file mode 100644 index 0000000000..a40e6ee0f7 --- /dev/null +++ b/doc/todo/specify_freeze__47__thaw_scripts_relative_to_topdir/comment_7_8fd25d5d2b802c6ff6ee0a3802d6f1cb._comment @@ -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. +"""]]