2010-10-27 17:12:02 +00:00
|
|
|
{- git repository command queue
|
2010-10-27 20:53:54 +00:00
|
|
|
-
|
2021-01-04 16:51:55 +00:00
|
|
|
- Copyright 2010-2021 Joey Hess <id@joeyh.name>
|
2010-10-27 20:53:54 +00:00
|
|
|
-
|
2019-03-13 19:48:14 +00:00
|
|
|
- Licensed under the GNU AGPL version 3 or higher.
|
2010-10-26 19:59:50 +00:00
|
|
|
-}
|
|
|
|
|
2013-10-17 19:56:56 +00:00
|
|
|
{-# LANGUAGE CPP, BangPatterns #-}
|
2012-02-15 15:13:13 +00:00
|
|
|
|
2011-06-30 17:25:37 +00:00
|
|
|
module Git.Queue (
|
2010-10-26 19:59:50 +00:00
|
|
|
Queue,
|
2011-12-20 18:37:53 +00:00
|
|
|
new,
|
2012-06-07 19:19:44 +00:00
|
|
|
addCommand,
|
|
|
|
addUpdateIndex,
|
2018-08-17 17:24:52 +00:00
|
|
|
addInternalAction,
|
|
|
|
InternalActionRunner(..),
|
2011-03-16 19:10:15 +00:00
|
|
|
size,
|
2011-04-07 17:59:31 +00:00
|
|
|
full,
|
2012-02-15 15:13:13 +00:00
|
|
|
flush,
|
2015-11-05 22:21:48 +00:00
|
|
|
merge,
|
2010-10-26 19:59:50 +00:00
|
|
|
) where
|
|
|
|
|
2012-02-01 20:05:02 +00:00
|
|
|
import Utility.SafeCommand
|
2011-12-20 18:37:53 +00:00
|
|
|
import Common
|
2011-06-30 17:25:37 +00:00
|
|
|
import Git
|
2011-12-14 19:56:11 +00:00
|
|
|
import Git.Command
|
2012-06-07 19:19:44 +00:00
|
|
|
import qualified Git.UpdateIndex
|
2013-10-17 19:56:56 +00:00
|
|
|
|
2018-04-22 17:28:31 +00:00
|
|
|
import qualified Data.Map.Strict as M
|
2019-11-12 14:44:51 +00:00
|
|
|
import Control.Monad.IO.Class
|
2014-02-25 18:09:39 +00:00
|
|
|
|
2015-11-05 22:21:48 +00:00
|
|
|
{- Queable actions that can be performed in a git repository. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
data Action m
|
2012-06-07 19:19:44 +00:00
|
|
|
{- Updating the index file, using a list of streamers that can
|
|
|
|
- be added to as the queue grows. -}
|
2015-11-05 22:21:48 +00:00
|
|
|
= UpdateIndexAction [Git.UpdateIndex.Streamer] -- in reverse order
|
2012-06-07 19:19:44 +00:00
|
|
|
{- A git command to run, on a list of files that can be added to
|
|
|
|
- as the queue grows. -}
|
|
|
|
| CommandAction
|
2021-01-04 16:51:55 +00:00
|
|
|
{ getCommonParams :: [CommandParam]
|
|
|
|
-- ^ parameters that come before the git subcommand
|
|
|
|
-- (in addition to the Repo's gitGlobalOpts.
|
|
|
|
, getSubcommand :: String
|
2012-06-07 19:19:44 +00:00
|
|
|
, getParams :: [CommandParam]
|
2021-01-04 16:51:55 +00:00
|
|
|
-- ^ parameters that come after the git subcommand
|
2018-08-17 17:24:52 +00:00
|
|
|
, getFiles :: [CommandParam]
|
|
|
|
}
|
|
|
|
{- An internal action to run, on a list of files that can be added
|
|
|
|
- to as the queue grows. -}
|
|
|
|
| InternalAction
|
2019-11-12 14:44:51 +00:00
|
|
|
{ getRunner :: InternalActionRunner m
|
2020-10-29 18:20:57 +00:00
|
|
|
, getInternalFiles :: [(RawFilePath, IO Bool)]
|
2018-08-16 18:28:05 +00:00
|
|
|
}
|
2010-10-26 19:59:50 +00:00
|
|
|
|
2018-08-17 17:24:52 +00:00
|
|
|
{- The String must be unique for each internal action. -}
|
2020-10-29 18:20:57 +00:00
|
|
|
data InternalActionRunner m = InternalActionRunner String (Repo -> [(RawFilePath, IO Bool)] -> m ())
|
2018-08-17 17:24:52 +00:00
|
|
|
|
2019-11-12 14:44:51 +00:00
|
|
|
instance Eq (InternalActionRunner m) where
|
2018-08-17 17:24:52 +00:00
|
|
|
InternalActionRunner s1 _ == InternalActionRunner s2 _ = s1 == s2
|
|
|
|
|
2012-06-07 19:19:44 +00:00
|
|
|
{- A key that can uniquely represent an action in a Map. -}
|
2021-01-04 16:38:43 +00:00
|
|
|
data ActionKey
|
|
|
|
= UpdateIndexActionKey
|
2021-01-04 16:51:55 +00:00
|
|
|
| CommandActionKey [CommandParam] String [CommandParam]
|
2021-01-04 16:38:43 +00:00
|
|
|
| InternalActionKey String
|
2012-06-07 19:19:44 +00:00
|
|
|
deriving (Eq, Ord)
|
2010-10-26 19:59:50 +00:00
|
|
|
|
2019-11-12 14:44:51 +00:00
|
|
|
actionKey :: Action m -> ActionKey
|
2012-06-07 19:19:44 +00:00
|
|
|
actionKey (UpdateIndexAction _) = UpdateIndexActionKey
|
2021-01-04 16:51:55 +00:00
|
|
|
actionKey CommandAction { getCommonParams = c, getSubcommand = s, getParams = p } = CommandActionKey c s p
|
2018-08-17 17:24:52 +00:00
|
|
|
actionKey InternalAction { getRunner = InternalActionRunner s _ } = InternalActionKey s
|
2012-06-05 00:41:22 +00:00
|
|
|
|
2010-10-26 19:59:50 +00:00
|
|
|
{- A queue of actions to perform (in any order) on a git repository,
|
|
|
|
- with lists of files to perform them on. This allows coalescing
|
|
|
|
- similar git commands. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
data Queue m = Queue
|
2012-02-15 15:13:13 +00:00
|
|
|
{ size :: Int
|
|
|
|
, _limit :: Int
|
2019-11-12 14:44:51 +00:00
|
|
|
, items :: M.Map ActionKey (Action m)
|
2012-02-15 15:13:13 +00:00
|
|
|
}
|
2010-10-26 19:59:50 +00:00
|
|
|
|
2011-04-07 17:59:31 +00:00
|
|
|
{- A recommended maximum size for the queue, after which it should be
|
|
|
|
- run.
|
|
|
|
-
|
|
|
|
- 10240 is semi-arbitrary. If we assume git filenames are between 10 and
|
|
|
|
- 255 characters long, then the queue will build up between 100kb and
|
|
|
|
- 2550kb long commands. The max command line length on linux is somewhere
|
|
|
|
- above 20k, so this is a fairly good balance -- the queue will buffer
|
|
|
|
- only a few megabytes of stuff and a minimal number of commands will be
|
|
|
|
- run by xargs. -}
|
2012-02-15 15:13:13 +00:00
|
|
|
defaultLimit :: Int
|
|
|
|
defaultLimit = 10240
|
2011-04-07 17:59:31 +00:00
|
|
|
|
2010-10-26 19:59:50 +00:00
|
|
|
{- Constructor for empty queue. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
new :: Maybe Int -> Queue m
|
2012-02-15 15:13:13 +00:00
|
|
|
new lim = Queue 0 (fromMaybe defaultLimit lim) M.empty
|
2010-10-26 19:59:50 +00:00
|
|
|
|
2012-06-07 19:40:44 +00:00
|
|
|
{- Adds an git command to the queue.
|
2012-06-07 19:19:44 +00:00
|
|
|
-
|
2012-06-07 19:40:44 +00:00
|
|
|
- Git commands with the same subcommand but different parameters are
|
|
|
|
- assumed to be equivilant enough to perform in any order with the same
|
2021-01-04 16:51:55 +00:00
|
|
|
- end result.
|
2012-06-07 19:19:44 +00:00
|
|
|
-}
|
2021-01-04 16:51:55 +00:00
|
|
|
addCommand :: MonadIO m => [CommandParam] -> String -> [CommandParam] -> [FilePath] -> Queue m -> Repo -> m (Queue m)
|
|
|
|
addCommand commonparams subcommand params files q repo =
|
2014-06-18 21:23:36 +00:00
|
|
|
updateQueue action different (length files) q repo
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
action = CommandAction
|
2021-01-04 16:51:55 +00:00
|
|
|
{ getCommonParams = commonparams
|
|
|
|
, getSubcommand = subcommand
|
2012-12-13 04:24:19 +00:00
|
|
|
, getParams = params
|
2018-08-17 17:24:52 +00:00
|
|
|
, getFiles = map File files
|
2012-12-13 04:24:19 +00:00
|
|
|
}
|
2014-06-18 21:23:36 +00:00
|
|
|
|
2012-12-13 04:24:19 +00:00
|
|
|
different (CommandAction { getSubcommand = s }) = s /= subcommand
|
|
|
|
different _ = True
|
2012-06-07 19:19:44 +00:00
|
|
|
|
2018-08-17 17:24:52 +00:00
|
|
|
{- Adds an internal action to the queue. -}
|
2020-10-29 18:20:57 +00:00
|
|
|
addInternalAction :: MonadIO m => InternalActionRunner m -> [(RawFilePath, IO Bool)] -> Queue m -> Repo -> m (Queue m)
|
2018-08-17 17:24:52 +00:00
|
|
|
addInternalAction runner files q repo =
|
|
|
|
updateQueue action different (length files) q repo
|
|
|
|
where
|
|
|
|
action = InternalAction
|
|
|
|
{ getRunner = runner
|
|
|
|
, getInternalFiles = files
|
|
|
|
}
|
|
|
|
|
|
|
|
different (InternalAction { getRunner = r }) = r /= runner
|
|
|
|
different _ = True
|
|
|
|
|
2012-06-10 17:56:04 +00:00
|
|
|
{- Adds an update-index streamer to the queue. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
addUpdateIndex :: MonadIO m => Git.UpdateIndex.Streamer -> Queue m -> Repo -> m (Queue m)
|
2012-06-07 19:19:44 +00:00
|
|
|
addUpdateIndex streamer q repo =
|
2012-06-10 17:56:04 +00:00
|
|
|
updateQueue action different 1 q repo
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
-- the list is built in reverse order
|
2015-11-05 22:21:48 +00:00
|
|
|
action = UpdateIndexAction [streamer]
|
2012-06-07 19:19:44 +00:00
|
|
|
|
2012-12-13 04:24:19 +00:00
|
|
|
different (UpdateIndexAction _) = False
|
|
|
|
different _ = True
|
2012-06-07 19:19:44 +00:00
|
|
|
|
|
|
|
{- Updates or adds an action in the queue. If the queue already contains a
|
|
|
|
- different action, it will be flushed; this is to ensure that conflicting
|
|
|
|
- actions, like add and rm, are run in the right order.-}
|
2019-11-12 14:44:51 +00:00
|
|
|
updateQueue :: MonadIO m => Action m -> (Action m -> Bool) -> Int -> Queue m -> Repo -> m (Queue m)
|
2012-06-13 01:13:15 +00:00
|
|
|
updateQueue !action different sizeincrease q repo
|
2012-06-07 19:19:44 +00:00
|
|
|
| null (filter different (M.elems (items q))) = return $ go q
|
|
|
|
| otherwise = go <$> flush q repo
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
|
|
|
go q' = newq
|
|
|
|
where
|
|
|
|
!newq = q'
|
|
|
|
{ size = newsize
|
|
|
|
, items = newitems
|
|
|
|
}
|
|
|
|
!newsize = size q' + sizeincrease
|
2018-04-22 17:28:31 +00:00
|
|
|
!newitems = M.insertWith combineNewOld (actionKey action) action (items q')
|
2015-11-05 22:21:48 +00:00
|
|
|
|
2018-08-17 17:19:37 +00:00
|
|
|
{- The new value comes first. It probably has a smaller list of files than
|
|
|
|
- the old value. So, the list append of the new value first is more
|
|
|
|
- efficient. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
combineNewOld :: Action m -> Action m -> Action m
|
2021-01-04 16:51:55 +00:00
|
|
|
combineNewOld (CommandAction _cps1 _sc1 _ps1 fs1) (CommandAction cps2 sc2 ps2 fs2) =
|
|
|
|
CommandAction cps2 sc2 ps2 (fs1++fs2)
|
2015-11-05 22:21:48 +00:00
|
|
|
combineNewOld (UpdateIndexAction s1) (UpdateIndexAction s2) =
|
|
|
|
UpdateIndexAction (s1++s2)
|
2018-08-17 17:24:52 +00:00
|
|
|
combineNewOld (InternalAction _r1 fs1) (InternalAction r2 fs2) =
|
|
|
|
InternalAction r2 (fs1++fs2)
|
2015-11-05 22:21:48 +00:00
|
|
|
combineNewOld anew _aold = anew
|
|
|
|
|
|
|
|
{- Merges the contents of the second queue into the first.
|
|
|
|
- This should only be used when the two queues are known to contain
|
|
|
|
- non-conflicting actions. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
merge :: Queue m -> Queue m -> Queue m
|
2015-11-05 22:21:48 +00:00
|
|
|
merge origq newq = origq
|
|
|
|
{ size = size origq + size newq
|
|
|
|
, items = M.unionWith combineNewOld (items newq) (items origq)
|
|
|
|
}
|
2010-10-26 19:59:50 +00:00
|
|
|
|
2011-04-07 17:59:31 +00:00
|
|
|
{- Is a queue large enough that it should be flushed? -}
|
2019-11-12 14:44:51 +00:00
|
|
|
full :: Queue m -> Bool
|
2016-01-13 18:55:01 +00:00
|
|
|
full (Queue cur lim _) = cur >= lim
|
2011-04-07 17:59:31 +00:00
|
|
|
|
2010-10-26 19:59:50 +00:00
|
|
|
{- Runs a queue on a git repository. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
flush :: MonadIO m => Queue m -> Repo -> m (Queue m)
|
2012-02-15 15:13:13 +00:00
|
|
|
flush (Queue _ lim m) repo = do
|
2012-06-07 19:19:44 +00:00
|
|
|
forM_ (M.elems m) $ runAction repo
|
2012-02-15 15:13:13 +00:00
|
|
|
return $ Queue 0 lim M.empty
|
2010-10-26 19:59:50 +00:00
|
|
|
|
|
|
|
{- Runs an Action on a list of files in a git repository.
|
|
|
|
-
|
2011-07-14 20:56:06 +00:00
|
|
|
- Complicated by commandline length limits.
|
|
|
|
-
|
|
|
|
- Intentionally runs the command even if the list of files is empty;
|
|
|
|
- this allows queueing commands that do not need a list of files. -}
|
2019-11-12 14:44:51 +00:00
|
|
|
runAction :: MonadIO m => Repo -> Action m -> m ()
|
2012-06-07 19:40:44 +00:00
|
|
|
runAction repo (UpdateIndexAction streamers) =
|
2012-06-13 01:13:15 +00:00
|
|
|
-- list is stored in reverse order
|
2019-11-12 14:44:51 +00:00
|
|
|
liftIO $ Git.UpdateIndex.streamUpdateIndex repo $ reverse streamers
|
|
|
|
runAction repo action@(CommandAction {}) = liftIO $ do
|
2013-10-17 19:56:56 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2020-06-04 19:36:34 +00:00
|
|
|
let p = (proc "xargs" $ "-0":"git":toCommand gitparams)
|
|
|
|
{ env = gitEnv repo
|
|
|
|
, std_in = CreatePipe
|
|
|
|
}
|
|
|
|
withCreateProcess p (go p)
|
2013-10-17 19:56:56 +00:00
|
|
|
#else
|
2016-06-02 01:46:58 +00:00
|
|
|
-- Using xargs on Windows is problematic, so just run the command
|
2013-10-17 19:56:56 +00:00
|
|
|
-- once per file (not as efficient.)
|
|
|
|
if null (getFiles action)
|
2014-06-12 22:37:12 +00:00
|
|
|
then void $ boolSystemEnv "git" gitparams (gitEnv repo)
|
2018-08-17 17:24:52 +00:00
|
|
|
else forM_ (getFiles action) $ \f ->
|
|
|
|
void $ boolSystemEnv "git" (gitparams ++ [f]) (gitEnv repo)
|
2013-10-17 19:56:56 +00:00
|
|
|
#endif
|
2012-12-13 04:24:19 +00:00
|
|
|
where
|
2013-10-17 19:56:56 +00:00
|
|
|
gitparams = gitCommandLine
|
2021-01-04 16:51:55 +00:00
|
|
|
(getCommonParams action++Param (getSubcommand action):getParams action)
|
|
|
|
repo
|
2020-11-23 18:00:17 +00:00
|
|
|
#ifndef mingw32_HOST_OS
|
2020-06-05 20:38:11 +00:00
|
|
|
go p (Just h) _ _ pid = do
|
2020-06-04 19:36:34 +00:00
|
|
|
hPutStr h $ intercalate "\0" $ toCommand $ getFiles action
|
|
|
|
hClose h
|
|
|
|
forceSuccessProcess p pid
|
|
|
|
go _ _ _ _ _ = error "internal"
|
2020-11-23 18:00:17 +00:00
|
|
|
#endif
|
2018-08-17 17:24:52 +00:00
|
|
|
runAction repo action@(InternalAction {}) =
|
|
|
|
let InternalActionRunner _ runner = getRunner action
|
|
|
|
in runner repo (getInternalFiles action)
|