Skip to content

Commit 94f2ba0

Browse files
committed
Don't use git ls-remote
Instead we initialize a local repository. This is a bit slowly (about 1s slower) but allows us to gain more information about the repo.
1 parent 303f442 commit 94f2ba0

1 file changed

Lines changed: 45 additions & 43 deletions

File tree

src/Niv/Git/Cmd.hs

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
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

1011
import Control.Applicative
1112
import Control.Arrow
13+
import Control.Monad (unless, void)
1214
import qualified Data.Aeson as Aeson
1315
import qualified Data.ByteString.Char8 as B8
1416
import qualified Data.HashMap.Strict as HMS
@@ -23,6 +25,7 @@ import qualified Options.Applicative as Opts
2325
import qualified Options.Applicative.Help.Pretty as Opts
2426
import System.Exit (ExitCode (ExitSuccess))
2527
import System.Process (readProcessWithExitCode)
28+
import UnliftIO
2629

2730
gitCmd :: Cmd
2831
gitCmd =
@@ -141,54 +144,37 @@ gitUpdate latestRev' defaultRefAndHEAD' = proc () -> do
141144
gitUpdate' :: Update () ()
142145
gitUpdate' = 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
169173
defaultRefAndHEAD ::
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

193179
abortNoRev :: [T.Text] -> T.Text -> IO a
194180
abortNoRev 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+
212214
isRev :: T.Text -> Bool
213215
isRev t =
214216
-- commit hashes are comprised of abcdef0123456789
@@ -219,7 +221,7 @@ isRev t =
219221

220222
abortGitFailure :: [T.Text] -> T.Text -> IO a
221223
abortGitFailure 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

Comments
 (0)