{-# LANGUAGE Arrows #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}

module Niv.Git.Cmd where

import Control.Applicative
import Control.Arrow
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Key as K
import qualified Data.Aeson.KeyMap as KM
import qualified Data.ByteString.Char8 as B8
import qualified Data.HashMap.Strict as HMS
import Data.Maybe
import qualified Data.Text as T
import Data.Text.Extended as T
import Niv.Cmd
import Niv.Logger
import Niv.Sources
import Niv.Update
import qualified Options.Applicative as Opts
import qualified Options.Applicative.Help.Pretty as Opts
import System.Exit (ExitCode (ExitSuccess))
import System.Process (readProcessWithExitCode)

gitCmd :: Cmd
gitCmd :: Cmd
gitCmd =
  Cmd
    { description :: forall a. InfoMod a
description = forall a. InfoMod a
describeGit,
      parseCmdShortcut :: Text -> Maybe (PackageName, Object)
parseCmdShortcut = Text -> Maybe (PackageName, Object)
parseGitShortcut,
      parsePackageSpec :: Parser PackageSpec
parsePackageSpec = Parser PackageSpec
parseGitPackageSpec,
      updateCmd :: Update () ()
updateCmd = Update () ()
gitUpdate',
      name :: Text
name = Text
"git",
      extraLogs :: Attrs -> [Text]
extraLogs = Attrs -> [Text]
gitExtraLogs
    }

gitExtraLogs :: Attrs -> [T.Text]
gitExtraLogs :: Attrs -> [Text]
gitExtraLogs Attrs
attrs = [Text]
noteRef forall a. Semigroup a => a -> a -> a
<> [Text]
warnRefBranch forall a. Semigroup a => a -> a -> a
<> [Text]
warnRefTag
  where
    noteRef :: [Text]
noteRef =
      forall {a}. Bool -> a -> [a]
textIf (forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
HMS.member Text
"ref" Attrs
attrs) forall a b. (a -> b) -> a -> b
$
        Text -> Text
mkNote
          Text
"Your source contains a `ref` attribute. Make sure your sources.nix is up-to-date and consider using a `branch` or `tag` attribute."
    warnRefBranch :: [Text]
warnRefBranch =
      forall {a}. Bool -> a -> [a]
textIf (Text -> Bool
member Text
"ref" Bool -> Bool -> Bool
&& Text -> Bool
member Text
"branch") forall a b. (a -> b) -> a -> b
$
        Text -> Text
mkWarn
          Text
"Your source contains both a `ref` and a `branch`. Niv will update the `branch` but the `ref` will be used by Nix to fetch the repo."
    warnRefTag :: [Text]
warnRefTag =
      forall {a}. Bool -> a -> [a]
textIf (Text -> Bool
member Text
"ref" Bool -> Bool -> Bool
&& Text -> Bool
member Text
"tag") forall a b. (a -> b) -> a -> b
$
        Text -> Text
mkWarn
          Text
"Your source contains both a `ref` and a `tag`. The `ref` will be used by Nix to fetch the repo."
    member :: Text -> Bool
member Text
x = forall k a. (Eq k, Hashable k) => k -> HashMap k a -> Bool
HMS.member Text
x Attrs
attrs
    textIf :: Bool -> a -> [a]
textIf Bool
cond a
txt = if Bool
cond then [a
txt] else []

parseGitShortcut :: T.Text -> Maybe (PackageName, Aeson.Object)
parseGitShortcut :: Text -> Maybe (PackageName, Object)
parseGitShortcut txt' :: Text
txt'@((Char -> Bool) -> Text -> Text
T.dropWhileEnd (forall a. Eq a => a -> a -> Bool
== Char
'/') -> Text
txt) =
  -- basic heuristics for figuring out if something is a git repo
  if Bool
isGitURL
    then case Text -> Text -> [Text]
T.splitOn Text
"/" Text
txt of
      [] -> forall a. Maybe a
Nothing
      (forall a. [a] -> a
last -> Text
w) -> case Text -> Text -> Maybe Text
T.stripSuffix Text
".git" Text
w of
        Maybe Text
Nothing -> forall a. a -> Maybe a
Just (Text -> PackageName
PackageName Text
w, forall v. Key -> v -> KeyMap v
KM.singleton Key
"repo" (Text -> Value
Aeson.String Text
txt'))
        Just Text
w' -> forall a. a -> Maybe a
Just (Text -> PackageName
PackageName Text
w', forall v. Key -> v -> KeyMap v
KM.singleton Key
"repo" (Text -> Value
Aeson.String Text
txt'))
    else forall a. Maybe a
Nothing
  where
    isGitURL :: Bool
isGitURL =
      Text
".git"
        Text -> Text -> Bool
`T.isSuffixOf` Text
txt
        Bool -> Bool -> Bool
|| Text
"git@"
        Text -> Text -> Bool
`T.isPrefixOf` Text
txt
        Bool -> Bool -> Bool
|| Text
"ssh://"
        Text -> Text -> Bool
`T.isPrefixOf` Text
txt

parseGitPackageSpec :: Opts.Parser PackageSpec
parseGitPackageSpec :: Parser PackageSpec
parseGitPackageSpec =
  (Object -> PackageSpec
PackageSpec forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall v. [(Key, v)] -> KeyMap v
KM.fromList)
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (f :: * -> *) a. Alternative f => f a -> f [a]
many (Parser (Key, Value)
parseRepo forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser (Key, Value)
parseBranch forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser (Key, Value)
parseRev forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser (Key, Value)
parseAttr forall (f :: * -> *) a. Alternative f => f a -> f a -> f a
<|> Parser (Key, Value)
parseSAttr)
  where
    parseRepo :: Parser (Key, Value)
parseRepo =
      (Key
"repo",) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Value
Aeson.String
        forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s. IsString s => Mod OptionFields s -> Parser s
Opts.strOption
          ( forall (f :: * -> *) a. HasName f => String -> Mod f a
Opts.long String
"repo"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
Opts.metavar String
"URL"
          )
    parseRev :: Parser (Key, Value)
parseRev =
      (Key
"rev",) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Value
Aeson.String
        forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s. IsString s => Mod OptionFields s -> Parser s
Opts.strOption
          ( forall (f :: * -> *) a. HasName f => String -> Mod f a
Opts.long String
"rev"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
Opts.metavar String
"SHA"
          )
    parseBranch :: Parser (Key, Value)
parseBranch =
      (Key
"branch",) forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Value
Aeson.String
        forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall s. IsString s => Mod OptionFields s -> Parser s
Opts.strOption
          ( forall (f :: * -> *) a. HasName f => String -> Mod f a
Opts.long String
"branch"
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => Char -> Mod f a
Opts.short Char
'b'
              forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
Opts.metavar String
"BRANCH"
          )
    parseAttr :: Parser (Key, Value)
parseAttr =
      forall a. ReadM a -> Mod OptionFields a -> Parser a
Opts.option
        (forall a. (String -> Maybe a) -> ReadM a
Opts.maybeReader String -> Maybe (Key, Value)
parseKeyValJSON)
        ( forall (f :: * -> *) a. HasName f => String -> Mod f a
Opts.long String
"attribute"
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => Char -> Mod f a
Opts.short Char
'a'
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
Opts.metavar String
"KEY=VAL"
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. String -> Mod f a
Opts.help String
"Set the package spec attribute <KEY> to <VAL>, where <VAL> may be JSON."
        )
    parseSAttr :: Parser (Key, Value)
parseSAttr =
      forall a. ReadM a -> Mod OptionFields a -> Parser a
Opts.option
        (forall a. (String -> Maybe a) -> ReadM a
Opts.maybeReader ((String -> Value) -> String -> Maybe (Key, Value)
parseKeyVal forall a. ToJSON a => a -> Value
Aeson.toJSON))
        ( forall (f :: * -> *) a. HasName f => String -> Mod f a
Opts.long String
"string-attribute"
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasName f => Char -> Mod f a
Opts.short Char
's'
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. HasMetavar f => String -> Mod f a
Opts.metavar String
"KEY=VAL"
            forall a. Semigroup a => a -> a -> a
<> forall (f :: * -> *) a. String -> Mod f a
Opts.help String
"Set the package spec attribute <KEY> to <VAL>."
        )
    parseKeyValJSON :: String -> Maybe (Key, Value)
parseKeyValJSON = (String -> Value) -> String -> Maybe (Key, Value)
parseKeyVal forall a b. (a -> b) -> a -> b
$ \String
x ->
      forall a. a -> Maybe a -> a
fromMaybe (forall a. ToJSON a => a -> Value
Aeson.toJSON String
x) (forall a. FromJSON a => ByteString -> Maybe a
Aeson.decodeStrict (String -> ByteString
B8.pack String
x))
    -- Parse "key=val" into ("key", val)
    parseKeyVal ::
      -- how to convert to JSON
      (String -> Aeson.Value) ->
      String ->
      Maybe (K.Key, Aeson.Value)
    parseKeyVal :: (String -> Value) -> String -> Maybe (Key, Value)
parseKeyVal String -> Value
toJSON String
str = case forall a. (a -> Bool) -> [a] -> ([a], [a])
span (forall a. Eq a => a -> a -> Bool
/= Char
'=') String
str of
      (String
key, Char
'=' : String
val) -> forall a. a -> Maybe a
Just (String -> Key
K.fromString String
key, String -> Value
toJSON String
val)
      (String, String)
_ -> forall a. Maybe a
Nothing

describeGit :: Opts.InfoMod a
describeGit :: forall a. InfoMod a
describeGit =
  forall a. Monoid a => [a] -> a
mconcat
    [ forall a. InfoMod a
Opts.fullDesc,
      forall a. String -> InfoMod a
Opts.progDesc String
"Add a git dependency. Experimental.",
      forall a. Maybe Doc -> InfoMod a
Opts.headerDoc forall a b. (a -> b) -> a -> b
$
        forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$
          Doc
"Examples:"
            Doc -> Doc -> Doc
Opts.<$$> Doc
""
            Doc -> Doc -> Doc
Opts.<$$> Doc
"  niv add git git@github.com:stedolan/jq"
            Doc -> Doc -> Doc
Opts.<$$> Doc
"  niv add git ssh://git@github.com/stedolan/jq --rev deadb33f"
            Doc -> Doc -> Doc
Opts.<$$> Doc
"  niv add git https://github.com/stedolan/jq.git"
            Doc -> Doc -> Doc
Opts.<$$> Doc
"  niv add git --repo /my/custom/repo --name custom --branch development"
    ]

gitUpdate ::
  -- | latest rev
  (T.Text -> T.Text -> IO T.Text) ->
  -- | latest rev and default ref
  (T.Text -> IO (T.Text, T.Text)) ->
  Update () ()
gitUpdate :: (Text -> Text -> IO Text)
-> (Text -> IO (Text, Text)) -> Update () ()
gitUpdate Text -> Text -> IO Text
latestRev' Text -> IO (Text, Text)
defaultBranchAndRev' = proc () -> do
  forall a. JSON a => Text -> Update (Box a) (Box a)
useOrSet Text
"type" -< (Box Text
"git" :: Box T.Text)
  Box Text
repository <- forall a. FromJSON a => Text -> Update () (Box a)
load Text
"repo" -< ()
  Update (Box Text) ()
discoverRev forall (a :: * -> * -> *) b c.
ArrowPlus a =>
a b c -> a b c -> a b c
<+> Update (Box Text) ()
discoverRefAndRev -< Box Text
repository
  where
    discoverRefAndRev :: Update (Box Text) ()
discoverRefAndRev = proc Box Text
repository -> do
      Box (Text, Text)
branchAndRev <- forall a b. (a -> IO b) -> Update (Box a) (Box b)
run Text -> IO (Text, Text)
defaultBranchAndRev' -< Box Text
repository
      forall a. JSON a => Text -> Update (Box a) (Box a)
update Text
"branch" -< forall a b. (a, b) -> a
fst forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Box (Text, Text)
branchAndRev
      forall a. JSON a => Text -> Update (Box a) (Box a)
update Text
"rev" -< forall a b. (a, b) -> b
snd forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Box (Text, Text)
branchAndRev
      forall (a :: * -> * -> *) b. Arrow a => a b b
returnA -< ()
    discoverRev :: Update (Box Text) ()
discoverRev = proc Box Text
repository -> do
      Box Text
branch <- forall a. FromJSON a => Text -> Update () (Box a)
load Text
"branch" -< ()
      Box Text
rev <- forall a b. (a -> IO b) -> Update (Box a) (Box b)
run' (forall a b c. (a -> b -> c) -> (a, b) -> c
uncurry Text -> Text -> IO Text
latestRev') -< (,) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Box Text
repository forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Box Text
branch
      forall a. JSON a => Text -> Update (Box a) (Box a)
update Text
"rev" -< Box Text
rev
      forall (a :: * -> * -> *) b. Arrow a => a b b
returnA -< ()

-- | The "real" (IO) update
gitUpdate' :: Update () ()
gitUpdate' :: Update () ()
gitUpdate' = (Text -> Text -> IO Text)
-> (Text -> IO (Text, Text)) -> Update () ()
gitUpdate Text -> Text -> IO Text
latestRev Text -> IO (Text, Text)
defaultBranchAndRev

latestRev ::
  -- | the repository
  T.Text ->
  -- | the branch
  T.Text ->
  IO T.Text
latestRev :: Text -> Text -> IO Text
latestRev Text
repo Text
branch = do
  let gitArgs :: [Text]
gitArgs = [Text
"ls-remote", Text
repo, Text
"refs/heads/" forall a. Semigroup a => a -> a -> a
<> Text
branch]
  [Text]
sout <- [Text] -> IO [Text]
runGit [Text]
gitArgs
  case [Text]
sout of
    ls :: [Text]
ls@(Text
_ : Text
_ : [Text]
_) -> forall {a}. [Text] -> [Text] -> IO a
abortTooMuchOutput [Text]
gitArgs [Text]
ls
    (Text
l1 : []) -> [Text] -> Text -> IO Text
parseRev [Text]
gitArgs Text
l1
    [] -> forall {a}. [Text] -> IO a
abortNoOutput [Text]
gitArgs
  where
    parseRev :: [Text] -> Text -> IO Text
parseRev [Text]
args Text
l = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. [Text] -> Text -> IO a
abortNoRev [Text]
args Text
l) forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ do
      Text -> Maybe Text
checkRev forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> Text
T.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'\t') Text
l
    checkRev :: Text -> Maybe Text
checkRev Text
t = if Text -> Bool
isRev Text
t then forall a. a -> Maybe a
Just Text
t else forall a. Maybe a
Nothing
    abortNoOutput :: [Text] -> IO a
abortNoOutput [Text]
args =
      forall a. [Text] -> Text -> IO a
abortGitFailure
        [Text]
args
        forall a b. (a -> b) -> a -> b
$ Text
"Git didn't produce any output. Does the branch '" forall a. Semigroup a => a -> a -> a
<> Text
branch forall a. Semigroup a => a -> a -> a
<> Text
"' exist?"
    abortTooMuchOutput :: [Text] -> [Text] -> IO a
abortTooMuchOutput [Text]
args [Text]
ls =
      forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args forall a b. (a -> b) -> a -> b
$
        [Text] -> Text
T.unlines forall a b. (a -> b) -> a -> b
$
          [Text
"Git produced too much output:"] forall a. Semigroup a => a -> a -> a
<> forall a b. (a -> b) -> [a] -> [b]
map (Text
"  " forall a. Semigroup a => a -> a -> a
<>) [Text]
ls

defaultBranchAndRev ::
  -- | the repository
  T.Text ->
  IO (T.Text, T.Text)
defaultBranchAndRev :: Text -> IO (Text, Text)
defaultBranchAndRev Text
repo = do
  [Text]
sout <- [Text] -> IO [Text]
runGit [Text]
args
  case [Text]
sout of
    (Text
l1 : Text
l2 : [Text]
_) -> (,) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Text -> IO Text
parseBranch Text
l1 forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Text -> IO Text
parseRev Text
l2
    [Text]
_ ->
      forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args forall a b. (a -> b) -> a -> b
$
        [Text] -> Text
T.unlines forall a b. (a -> b) -> a -> b
$
          [ Text
"Could not read reference and revision from stdout:"
          ]
            forall a. Semigroup a => a -> a -> a
<> [Text]
sout
  where
    args :: [Text]
args = [Text
"ls-remote", Text
"--symref", Text
repo, Text
"HEAD"]
    parseBranch :: Text -> IO Text
parseBranch Text
l = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. [Text] -> Text -> IO a
abortNoRef [Text]
args Text
l) forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ do
      -- ref: refs/head/master\tHEAD -> master\tHEAD
      Text
refAndSym <- Text -> Text -> Maybe Text
T.stripPrefix Text
"ref: refs/heads/" Text
l
      let branch :: Text
branch = (Char -> Bool) -> Text -> Text
T.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'\t') Text
refAndSym
      if Text -> Bool
T.null Text
branch then forall a. Maybe a
Nothing else forall a. a -> Maybe a
Just Text
branch
    parseRev :: Text -> IO Text
parseRev Text
l = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a. [Text] -> Text -> IO a
abortNoRev [Text]
args Text
l) forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ do
      Text -> Maybe Text
checkRev forall a b. (a -> b) -> a -> b
$ (Char -> Bool) -> Text -> Text
T.takeWhile (forall a. Eq a => a -> a -> Bool
/= Char
'\t') Text
l
    checkRev :: Text -> Maybe Text
checkRev Text
t = if Text -> Bool
isRev Text
t then forall a. a -> Maybe a
Just Text
t else forall a. Maybe a
Nothing

abortNoRev :: [T.Text] -> T.Text -> IO a
abortNoRev :: forall a. [Text] -> Text -> IO a
abortNoRev [Text]
args Text
l = forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args forall a b. (a -> b) -> a -> b
$ Text
"Could not read revision from: " forall a. Semigroup a => a -> a -> a
<> Text
l

abortNoRef :: [T.Text] -> T.Text -> IO a
abortNoRef :: forall a. [Text] -> Text -> IO a
abortNoRef [Text]
args Text
l = forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args forall a b. (a -> b) -> a -> b
$ Text
"Could not read reference from: " forall a. Semigroup a => a -> a -> a
<> Text
l

-- | Run the "git" executable
runGit :: [T.Text] -> IO [T.Text]
runGit :: [Text] -> IO [Text]
runGit [Text]
args = do
  (ExitCode
exitCode, String
sout, String
serr) <- String -> [String] -> String -> IO (ExitCode, String, String)
readProcessWithExitCode String
"git" (Text -> String
T.unpack forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Text]
args) String
""
  case (ExitCode
exitCode, String -> [String]
lines String
sout) of
    (ExitCode
ExitSuccess, [String]
ls) -> forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ String -> Text
T.pack forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [String]
ls
    (ExitCode, [String])
_ ->
      forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args forall a b. (a -> b) -> a -> b
$
        [Text] -> Text
T.unlines
          [ [Text] -> Text
T.unwords [Text
"stdout:", String -> Text
T.pack String
sout],
            [Text] -> Text
T.unwords [Text
"stderr:", String -> Text
T.pack String
serr]
          ]

isRev :: T.Text -> Bool
isRev :: Text -> Bool
isRev Text
t =
  -- commit hashes are comprised of abcdef0123456789
  (Char -> Bool) -> Text -> Bool
T.all (\Char
c -> (Char
c forall a. Ord a => a -> a -> Bool
>= Char
'a' Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'f') Bool -> Bool -> Bool
|| (Char
c forall a. Ord a => a -> a -> Bool
>= Char
'0' Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'9')) Text
t
    Bool -> Bool -> Bool
&&
    -- commit _should_ be 40 chars long, but to be sure we pick 7
    Text -> Int
T.length Text
t forall a. Ord a => a -> a -> Bool
>= Int
7

abortGitFailure :: [T.Text] -> T.Text -> IO a
abortGitFailure :: forall a. [Text] -> Text -> IO a
abortGitFailure [Text]
args Text
msg =
  forall (io :: * -> *) a. MonadIO io => Text -> io a
abort forall a b. (a -> b) -> a -> b
$
    [Text] -> Text
T.unlines
      [ Text
"Could not read the output of 'git'.",
        [Text] -> Text
T.unwords (Text
"command:" forall a. a -> [a] -> [a]
: Text
"git" forall a. a -> [a] -> [a]
: [Text]
args),
        Text
msg
      ]

abortGitBug :: [T.Text] -> T.Text -> IO a
abortGitBug :: forall a. [Text] -> Text -> IO a
abortGitBug [Text]
args Text
msg =
  forall (io :: * -> *) a. MonadIO io => Text -> io a
abort forall a b. (a -> b) -> a -> b
$
    Text -> Text
bug forall a b. (a -> b) -> a -> b
$
      [Text] -> Text
T.unlines
        [ Text
"Could not read the output of 'git'.",
          [Text] -> Text
T.unwords (Text
"command:" forall a. a -> [a] -> [a]
: Text
"git" forall a. a -> [a] -> [a]
: [Text]
args),
          Text
msg
        ]