11{-# LANGUAGE Arrows #-}
2+ {-# LANGUAGE LambdaCase #-}
23{-# LANGUAGE OverloadedStrings #-}
34{-# LANGUAGE QuasiQuotes #-}
45{-# LANGUAGE ScopedTypeVariables #-}
@@ -9,6 +10,7 @@ module Niv.Git.Cmd where
910
1011import Control.Applicative
1112import Control.Arrow
13+ import Control.Monad (unless , void )
1214import qualified Data.Aeson as Aeson
1315import qualified Data.ByteString.Char8 as B8
1416import qualified Data.HashMap.Strict as HMS
@@ -23,6 +25,7 @@ import qualified Options.Applicative as Opts
2325import qualified Options.Applicative.Help.Pretty as Opts
2426import System.Exit (ExitCode (ExitSuccess ))
2527import System.Process (readProcessWithExitCode )
28+ import UnliftIO
2629
2730gitCmd :: Cmd
2831gitCmd =
@@ -141,54 +144,37 @@ gitUpdate latestRev' defaultRefAndHEAD' = proc () -> do
141144gitUpdate' :: Update () ()
142145gitUpdate' = gitUpdate latestRev defaultRefAndHEAD
143146
144- latestRev ::
145- -- | the repository
146- T. Text ->
147- -- | the ref/branch
148- T. Text ->
149- IO T. Text
150- latestRev repo ref = do
151- let gitArgs = [" ls-remote" , repo, " refs/heads/" <> ref]
152- sout <- runGit gitArgs
153- case sout of
154- ls@ (_ : _ : _) -> abortTooMuchOutput gitArgs ls
155- (l1 : [] ) -> parseRev gitArgs l1
156- [] -> abortNoOutput gitArgs
147+ -- TODO: document the git operations
148+ latestRevInfo :: T. Text -> Maybe T. Text -> IO (T. Text , T. Text )
149+ latestRevInfo repo mref = runGits $ \ git -> do
150+ void $ git [" init" ]
151+ void $ git [" remote" , " add" , " origin" , repo]
152+ ref <- maybe (git [" remote" , " show" , " origin" ] >>= findRef) pure mref
153+ void $ git [" fetch" , " origin" , ref, " --depth" , " 1" ]
154+ void $ git [" checkout" , ref]
155+ git [" show" , " --quiet" , " --format=%H%n%aD" , ref] >>= \ case
156+ [] -> abort " Git did not produce enough output while reading commit information"
157+ [rev, _date] -> do
158+ unless (isRev rev) $ do
159+ abort $ " The revision retrieved from git does not look like a revision: '" <> rev <> " '."
160+ pure (ref, rev)
161+ output ->
162+ abort $ T. unlines $
163+ [" Git produced too much output while reading commit information:" ] <> output
157164 where
158- parseRev args l = maybe (abortNoRev args l) pure $ do
159- checkRev $ T. takeWhile (/= ' \t ' ) l
160- checkRev t = if isRev t then Just t else Nothing
161- abortNoOutput args =
162- abortGitFailure
163- args
164- " Git didn't produce any output."
165- abortTooMuchOutput args ls =
166- abortGitFailure args $ T. unlines $
167- [" Git produced too much output:" ] <> map (" " <> ) ls
165+ findRef ls = case listToMaybe $ mapMaybe (T. stripPrefix " HEAD branch:" . T. strip) ls of
166+ Just l -> pure (T. strip l)
167+ Nothing -> abort $ T. unlines $ [" could not parse default ref: " ] <> ls
168168
169+ latestRev :: T. Text -> T. Text -> IO T. Text
170+ latestRev repo ref = snd <$> latestRevInfo repo (Just ref)
171+
172+ -- TODO: test this
169173defaultRefAndHEAD ::
170174 -- | the repository
171175 T. Text ->
172176 IO (T. Text , T. Text )
173- defaultRefAndHEAD repo = do
174- sout <- runGit args
175- case sout of
176- (l1 : l2 : _) -> (,) <$> parseRef l1 <*> parseRev l2
177- _ ->
178- abortGitFailure args $ T. unlines $
179- [ " Could not read reference and revision from stdout:"
180- ]
181- <> sout
182- where
183- args = [" ls-remote" , " --symref" , repo, " HEAD" ]
184- parseRef l = maybe (abortNoRef args l) pure $ do
185- -- ref: refs/head/master\tHEAD -> master\tHEAD
186- refAndSym <- T. stripPrefix " ref: refs/heads/" l
187- let ref = T. takeWhile (/= ' \t ' ) refAndSym
188- if T. null ref then Nothing else Just ref
189- parseRev l = maybe (abortNoRev args l) pure $ do
190- checkRev $ T. takeWhile (/= ' \t ' ) l
191- checkRev t = if isRev t then Just t else Nothing
177+ defaultRefAndHEAD repo = latestRevInfo repo Nothing
192178
193179abortNoRev :: [T. Text ] -> T. Text -> IO a
194180abortNoRev args l = abortGitFailure args $ " Could not read revision from: " <> l
@@ -209,6 +195,22 @@ runGit args = do
209195 T. unwords [" stderr:" , T. pack serr]
210196 ]
211197
198+ runGits :: (([T. Text ] -> IO [T. Text ]) -> IO a ) -> IO a
199+ runGits act = withSystemTempDirectory " niv" $ \ f -> do
200+ past <- newIORef []
201+ let runGit' args = do
202+ atomicModifyIORef past (\ past' -> (past' <> [args], () ))
203+ runGit (" -C" : T. pack f : args)
204+ tryAny (act runGit') >>= \ case
205+ Left e -> do
206+ past' <- readIORef past
207+ abort $ bug $ T. unlines $
208+ [ " An error happened while executing the following git commands in the niv checkout '" <> T. pack f <> " ':"
209+ ]
210+ <> (map (\ cmd -> T. intercalate " " (" git" : cmd)) past')
211+ <> [tshow e]
212+ Right a -> pure a
213+
212214isRev :: T. Text -> Bool
213215isRev t =
214216 -- commit hashes are comprised of abcdef0123456789
@@ -219,7 +221,7 @@ isRev t =
219221
220222abortGitFailure :: [T. Text ] -> T. Text -> IO a
221223abortGitFailure args msg =
222- abort $ bug $
224+ abort $
223225 T. unlines
224226 [ " Could not read the output of 'git'." ,
225227 T. unwords (" command:" : " git" : args),
0 commit comments