Skip to content

Commit 9165e6f

Browse files
committed
Implement type command to get gpg path
`type` command is only available as the shell command which means that we can't use `proc`. This means we should use `shell` instead. However, using `shell` is not safe, and escaping is needed to make sure args are mungled. This is the reason why I use `shell` only for `type` command and `proc` for everything else. #297
1 parent a144580 commit 9165e6f

File tree

7 files changed

+191
-146
lines changed

7 files changed

+191
-146
lines changed

.github/workflows/haskell-quality-pipeline.yml

-9
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,6 @@ jobs:
2626

2727
steps:
2828
- uses: actions/checkout@v3
29-
- uses: actions/cache@v3
30-
name: Cache .ghcup
31-
id: haskell-env-cache
32-
with:
33-
path: ~/.ghcup
34-
key: ${{ runner.os }}-ghcup-global-${{ env.GHCUP_VERSION }}-${{ env.STACK_VERSION }}
35-
restore-keys: |
36-
${{ runner.os }}-ghcup-global-${{ env.GHCUP_VERSION }}-
37-
${{ runner.os }}-ghcup-global-
3829
- uses: actions/cache@v3
3930
name: Cache .stack
4031
id: haskell-deps-cache

src/Elegit/Cli/Action/AcquireRepository.hs

+31-29
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,8 @@ configDefault cKey = case cKey of
8383
configureBasics :: (MonadFree GA.GitF m) => GA.ConfigScope -> m ()
8484
configureBasics cScope = do
8585
for_ basicConfigs $ \cKey -> do
86-
keyDefault <- configDefault cKey
87-
newValue <- GA.promptDefault (configPrompt cKey) keyDefault
86+
mKeyDefault <- configDefault cKey
87+
newValue <- GA.promptDefault (configPrompt cKey) mKeyDefault
8888
GA.setConfigVerbose cScope (configName cKey) newValue
8989

9090
where
@@ -150,36 +150,38 @@ configureAliases cScope = do
150150
]
151151

152152

153+
-- TODO: This could be improved syntastically if we use `mdo` instead of plain `do`
153154
setupGPGSignature :: (MonadFree GA.GitF m) => m ()
154155
setupGPGSignature = do
155156
whenJustM (GA.readConfig GA.LocalConfig (configName UserEmailKey)) $ \userEmail -> do
156-
GA.gpgListKeysVerbose userEmail >>= \case
157-
Nothing -> do
158-
GA.print =<< GA.formatInfo "There is no gpg key for the given email."
159-
GA.print =<< GA.formatInfo "A signature is not configured."
160-
Just gpgKeysOutput -> do
161-
mapM_ GA.print gpgKeysOutput
162-
GA.print ""
163-
GA.print =<< GA.formatInfo "From the list of GPG keys above, copy the GPG key ID you'd like to use."
164-
GA.print =<< GA.formatInfo "It will be"
165-
GA.print =<< GA.formatInfo " 3AA5C34371567BD2"
166-
GA.print =<< GA.formatInfo "for the output like this"
167-
GA.print =<< GA.formatInfo " sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]"
168-
GA.print =<< GA.formatInfo " A330C91F8EC4BC7AECFA63E03AA5C34371567BD2"
169-
GA.print =<< GA.formatInfo " uid Hubot"
170-
GA.print =<< GA.formatInfo ""
171-
GA.print =<< GA.formatInfo "If you don't want to configure signature, just hit Enter button."
172-
-- TODO: We could parse IDs out of the gpg output.
173-
-- Then could ask for the index into list of keys instead?
174-
key <- GA.promptDefault "Please pass a key that has to sign objects of the current repository: " (Just "")
175-
if null key
176-
then GA.print =<< GA.formatInfo "The signature is not configured as the empty key is provided."
177-
else do
178-
GA.setConfigVerbose GA.LocalConfig "user.signingkey" key
179-
GA.setConfigVerbose GA.LocalConfig "gpg.program" "$(type -p gpg)"
180-
GA.setConfigVerbose GA.LocalConfig "commit.gpgsign" "true"
181-
GA.setConfigVerbose GA.LocalConfig "tag.forceSignAnnotated" "true"
182-
GA.setConfigVerbose GA.LocalConfig "tag.gpgSign" "true"
157+
whenJustM (GA.pathToTool "gpg") $ \pathToGPG -> do
158+
GA.gpgListKeysVerbose userEmail >>= \case
159+
Nothing -> do
160+
GA.print =<< GA.formatInfo "There is no gpg key for the given email."
161+
GA.print =<< GA.formatInfo "A signature is not configured."
162+
Just gpgKeysOutput -> do
163+
mapM_ GA.print gpgKeysOutput
164+
GA.print ""
165+
GA.print =<< GA.formatInfo "From the list of GPG keys above, copy the GPG key ID you'd like to use."
166+
GA.print =<< GA.formatInfo "It will be"
167+
GA.print =<< GA.formatInfo " 3AA5C34371567BD2"
168+
GA.print =<< GA.formatInfo "for the output like this"
169+
GA.print =<< GA.formatInfo " sec 4096R/3AA5C34371567BD2 2016-03-10 [expires: 2017-03-10]"
170+
GA.print =<< GA.formatInfo " A330C91F8EC4BC7AECFA63E03AA5C34371567BD2"
171+
GA.print =<< GA.formatInfo " uid Hubot"
172+
GA.print =<< GA.formatInfo ""
173+
GA.print =<< GA.formatInfo "If you don't want to configure signature, just hit Enter button."
174+
-- TODO: We could parse IDs out of the gpg output.
175+
-- Then could ask for the index into list of keys instead?
176+
key <- GA.promptOneTime "Please pass a key that has to sign objects of the current repository"
177+
if null key
178+
then GA.print =<< GA.formatInfo "The signature is not configured as the empty key is provided."
179+
else do
180+
GA.setConfigVerbose GA.LocalConfig "user.signingkey" key
181+
GA.setConfigVerbose GA.LocalConfig "gpg.program" pathToGPG
182+
GA.setConfigVerbose GA.LocalConfig "commit.gpgsign" "true"
183+
GA.setConfigVerbose GA.LocalConfig "tag.forceSignAnnotated" "true"
184+
GA.setConfigVerbose GA.LocalConfig "tag.gpgSign" "true"
183185

184186

185187
-- | Execution description of the AcquireRepository action

src/Elegit/Git/Action.hs

+39-5
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ data GReadConfigData
9696
}
9797

9898
instance RenderGitCommand GReadConfigData where
99-
commandArgs (GReadConfigData cScope cName) = ["config", scopeText, "--get", cName]
99+
commandArgs (GReadConfigData cScope cName) =
100+
filter (not . null)
101+
["config", scopeText, "--get", cName]
100102
where
101103
scopeText :: Text
102104
scopeText = case cScope of
@@ -112,7 +114,9 @@ data GSetConfigData
112114
}
113115

114116
instance RenderGitCommand GSetConfigData where
115-
commandArgs (GSetConfigData cScope cName cValue) = ["config", scopeText, cName, "\""+|cValue|+"\""]
117+
commandArgs (GSetConfigData cScope cName cValue) =
118+
filter (not . null)
119+
["config", scopeText, cName, cValue]
116120
where
117121
scopeText :: Text
118122
scopeText = case cScope of
@@ -140,14 +144,22 @@ newtype GAliasesToRemoveData
140144

141145
instance RenderGitCommand GAliasesToRemoveData where
142146
commandArgs (GAliasesToRemoveData cScope) =
143-
["config", scopeText, "--name-only", "--get-regexp", "\"^alias.\"", "\"^elegant ([-a-z]+)$\""]
147+
["config", scopeText, "--name-only", "--get-regexp", "^alias.", "^elegant ([-a-z]+)$"]
144148
where
145149
scopeText :: Text
146150
scopeText = case cScope of
147151
GlobalConfig -> "--global"
148152
LocalConfig -> "--local"
149153
AutoConfig -> ""
150154

155+
newtype GPathToToolData
156+
= GPathToToolData { name :: Text }
157+
158+
instance RenderGitCommand GPathToToolData where
159+
toolName _ = "type"
160+
161+
commandArgs (GPathToToolData toolName') = ["-p", toolName']
162+
151163
newtype GGPGKeyListData
152164
= GGPGKeyListData { email :: Text }
153165

@@ -175,7 +187,8 @@ data GitF a
175187
| SetConfig GSetConfigData a
176188
| UnsetConfig GUnsetConfigData a
177189
| GPGListKeys GGPGKeyListData (Maybe (NonEmpty Text) -> a)
178-
| Prompt Text (Maybe Text) (Text -> a)
190+
| PathToTool GPathToToolData (Maybe Text -> a)
191+
| Prompt PromptConfig (Text -> a)
179192
| FormatInfo Text (Text -> a)
180193
| FormatCommand Text (Text -> a)
181194
| PrintText Text a
@@ -205,6 +218,21 @@ data LogType
205218
-- | Type alias to the `Free` `GitF` monad.
206219
type FreeGit t = F GitF t
207220

221+
222+
-- TODO: Make `OneTime` separate to improve the return type of the prompt
223+
-- OneTime should return `Maybe Text` instead of `Text` to indicate 2 possible states.
224+
-- Default would always return Text, as there is no possibity to go forward otherwise.
225+
data PromptType
226+
= PromptOneTime
227+
| PromptDefault (Maybe Text)
228+
229+
230+
data PromptConfig
231+
= PromptConfig
232+
{ question :: Text
233+
, promptType :: PromptType
234+
}
235+
208236
-- | You should consider following code as a boilerplate
209237
--
210238
-- Each command should have the associated function to simplify the usage of this API.
@@ -254,8 +282,14 @@ unsetConfig cScope cName = liftF $ UnsetConfig (GUnsetConfigData cScope cName) (
254282
gpgListKeys :: MonadFree GitF m => Text -> m (Maybe (NonEmpty Text))
255283
gpgListKeys gEmail = liftF $ GPGListKeys (GGPGKeyListData gEmail) id
256284

285+
pathToTool :: MonadFree GitF m => Text -> m (Maybe Text)
286+
pathToTool toolName' = liftF $ PathToTool (GPathToToolData toolName') id
287+
257288
promptDefault :: MonadFree GitF m => Text -> Maybe Text -> m Text
258-
promptDefault pText pDefault = liftF $ Prompt pText pDefault id
289+
promptDefault pText pDefault = liftF $ Prompt (PromptConfig pText (PromptDefault pDefault)) id
290+
291+
promptOneTime :: MonadFree GitF m => Text -> m Text
292+
promptOneTime pText = liftF $ Prompt (PromptConfig pText PromptOneTime) id
259293

260294
formatInfo :: MonadFree GitF m => Text -> m Text
261295
formatInfo content = liftF $ FormatInfo content id

src/Elegit/Git/Exec.hs

+20-13
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ module Elegit.Git.Exec where
22

33
import Control.Monad.Catch as MC
44
import Data.Text (stripEnd)
5+
import qualified Data.Text as T
56
import Elegit.Git.Action
67
import GHC.IO.Handle (hFlush)
7-
import System.Process.Typed (ExitCode (ExitFailure, ExitSuccess), ProcessConfig, proc, readProcess)
8+
import System.Process.Typed (ExitCode (ExitFailure, ExitSuccess), ProcessConfig, proc, readProcess, shell)
89
import Universum as U
910

1011

@@ -24,24 +25,30 @@ data GitCommand
2425
| GCUC GUnsetConfigData
2526
| GCATR GAliasesToRemoveData
2627
| GCGKL GGPGKeyListData
28+
| GCPTT GPathToToolData
2729

2830

29-
procText :: Text -> [Text] -> ProcessConfig () () ()
30-
procText name args = proc (toString name) (toString <$> args)
31+
procCmd :: Text -> [Text] -> ProcessConfig () () ()
32+
procCmd tName args = proc (toString tName) (toString <$> args)
33+
34+
35+
shellCmd :: Text -> [Text] -> ProcessConfig () () ()
36+
shellCmd tName args = shell $ toString $ T.intercalate " " (tName:args)
3137

3238

3339
-- TODO: cover with tests
3440
procFromCmd :: GitCommand -> ProcessConfig () () ()
35-
procFromCmd (GCCB gc) = procText (toolName gc) (commandArgs gc)
36-
procFromCmd (GCBU gc) = procText (toolName gc) (commandArgs gc)
37-
procFromCmd (GCL gc) = procText (toolName gc) ("-c":"color.ui=always":commandArgs gc)
38-
procFromCmd (GCS gc) = procText (toolName gc) ("-c":"color.status=always":commandArgs gc)
39-
procFromCmd (GCSL gc) = procText (toolName gc) (commandArgs gc)
40-
procFromCmd (GCRC gc) = procText (toolName gc) (commandArgs gc)
41-
procFromCmd (GCSC gc) = procText (toolName gc) (commandArgs gc)
42-
procFromCmd (GCUC gc) = procText (toolName gc) (commandArgs gc)
43-
procFromCmd (GCATR gc) = procText (toolName gc) (commandArgs gc)
44-
procFromCmd (GCGKL gc) = procText (toolName gc) (commandArgs gc)
41+
procFromCmd (GCCB gc) = procCmd (toolName gc) (commandArgs gc)
42+
procFromCmd (GCBU gc) = procCmd (toolName gc) (commandArgs gc)
43+
procFromCmd (GCL gc) = procCmd (toolName gc) ("-c":"color.ui=always":commandArgs gc)
44+
procFromCmd (GCS gc) = procCmd (toolName gc) ("-c":"color.status=always":commandArgs gc)
45+
procFromCmd (GCSL gc) = procCmd (toolName gc) (commandArgs gc)
46+
procFromCmd (GCRC gc) = procCmd (toolName gc) (commandArgs gc)
47+
procFromCmd (GCSC gc) = procCmd (toolName gc) (commandArgs gc)
48+
procFromCmd (GCUC gc) = procCmd (toolName gc) (commandArgs gc)
49+
procFromCmd (GCATR gc) = procCmd (toolName gc) (commandArgs gc)
50+
procFromCmd (GCGKL gc) = procCmd (toolName gc) (commandArgs gc)
51+
procFromCmd (GCPTT gc) = shellCmd (toolName gc) (commandArgs gc)
4552

4653

4754
class Monad m => MonadGitExec m where

src/Elegit/Git/Runner/Real.hs

+13-7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ executeGitF arg = case arg of
4141
stashes <- lines . fromMaybe "" <$> execGit (GCSL gc)
4242
return $ next stashes
4343

44+
GA.PathToTool gc next -> do
45+
path <- execGit (GCPTT gc)
46+
return $ next path
47+
4448
GA.GPGListKeys gc next -> do
4549
mGpgKeys <- execGit (GCGKL gc)
4650
return $ next (mGpgKeys >>= nonEmpty . lines)
@@ -55,22 +59,24 @@ executeGitF arg = case arg of
5559
GA.UnsetConfig gc next -> do
5660
U.void $ execGit (GCUC gc)
5761
return next
58-
GA.Prompt prompt pDefaultM next -> do
62+
GA.Prompt (GA.PromptConfig prompt pType) next -> do
5963
let
6064
askPrompt = do
6165
pText (colored Purple Normal message)
6266
gLine
6367

6468
message :: Text
6569
message =
66-
case pDefaultM of
67-
Just pDefault -> fmt ""+|prompt|+" {"+|pDefault|+"}: "
68-
Nothing -> fmt ""+|prompt|+": "
70+
case pType of
71+
GA.PromptOneTime -> fmt ""+|prompt|+": "
72+
GA.PromptDefault (Just pDefault) -> fmt ""+|prompt|+" {"+|pDefault|+"}: "
73+
GA.PromptDefault Nothing -> fmt ""+|prompt|+": "
6974

7075
answer <-
71-
case pDefaultM of
72-
Nothing -> until (not . null) askPrompt
73-
Just pDefault -> do
76+
case pType of
77+
GA.PromptOneTime -> askPrompt
78+
GA.PromptDefault Nothing -> until (not . null) askPrompt
79+
GA.PromptDefault (Just pDefault) -> do
7480
answer <- askPrompt
7581
if null answer
7682
then return pDefault

src/Elegit/Git/Runner/Simulated.hs

+13-8
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ collectImpureCommandsF cmd = case cmd of
207207
localConfig %= HS.delete cName
208208
return next
209209

210-
GA.Prompt prompt pDefaultM next -> do
210+
GA.Prompt (GA.PromptConfig prompt pType) next -> do
211211
let
212212
-- TODO: Make configurable
213213
hardcodedAnswer :: Text
@@ -219,19 +219,24 @@ collectImpureCommandsF cmd = case cmd of
219219

220220
message :: Text
221221
message =
222-
case pDefaultM of
223-
Just pDefault -> fmt ""+|prompt|+" {"+|pDefault|+"}: "
224-
Nothing -> fmt ""+|prompt|+": "
225-
226-
answer <- case pDefaultM of
227-
Nothing -> until (not . null) promptAnswer
228-
Just pDefault -> do
222+
case pType of
223+
GA.PromptOneTime -> fmt ""+|prompt|+": "
224+
GA.PromptDefault (Just pDefault) -> fmt ""+|prompt|+" {"+|pDefault|+"}: "
225+
GA.PromptDefault Nothing -> fmt ""+|prompt|+": "
226+
227+
answer <- case pType of
228+
GA.PromptOneTime -> promptAnswer
229+
GA.PromptDefault Nothing -> until (not . null) promptAnswer
230+
GA.PromptDefault (Just pDefault) -> do
229231
answer <- promptAnswer
230232
if null answer
231233
then return pDefault
232234
else return answer
233235
return $ next answer
234236

237+
GA.PathToTool (GA.GPathToToolData toolName) next -> do
238+
return $ next $ Just ("/usr/bin/"+|toolName|+"")
239+
235240
GA.FormatInfo content next -> do
236241
return $ next content
237242
GA.FormatCommand content next -> do

0 commit comments

Comments
 (0)