diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,12 @@
 # Revision history for simple-cmd
 
+## 0.2.2 (2020-06-17)
+- grep: no longer errors for no match
+- add cmdStderrToStdout: redirects stderr to stdout
+- add gitBool
+- define gitBranch using "git rev-parse" (internal change)
+- now export ifM and whenM from extra
+
 ## 0.2.1 (2019-12-12)
 - add cmdTry_: only runs if available
 - add ifM and whenM
diff --git a/SimpleCmd.hs b/SimpleCmd.hs
--- a/SimpleCmd.hs
+++ b/SimpleCmd.hs
@@ -39,6 +39,7 @@
   cmdStdIn,
   cmdStdErr,
   cmdTry_,
+  cmdStderrToStdout,
   error',
   egrep_, grep, grep_,
   ifM,
@@ -59,18 +60,19 @@
 #else
 import Control.Applicative ((<$>))
 #endif
-import Control.Monad
+import Control.Monad.Extra
 
 import Data.List (stripPrefix)
 import Data.Maybe (isJust, isNothing, fromMaybe)
 
 import System.Directory (findExecutable)
 import System.Exit (ExitCode (..))
-import System.IO (hGetContents, hPutStrLn, IOMode(ReadMode), stderr, withFile)
+import System.IO (hGetContents, hPutStrLn, IOMode(ReadMode), stderr, stdout,
+                  withFile)
 import System.Posix.User (getEffectiveUserID)
 import System.Process (createProcess, proc, ProcessHandle, rawSystem, readProcess,
                        readProcessWithExitCode, runProcess, showCommandForUser,
-                       std_in, std_out, StdStream(CreatePipe, UseHandle),
+                       std_err, std_in, std_out, StdStream(CreatePipe, UseHandle),
                        waitForProcess, withCreateProcess)
 
 removeTrailingNewline :: String -> String
@@ -93,13 +95,13 @@
 error' = error
 #endif
 
--- | 'cmd c args' runs a command in a process and returns stdout
+-- | @cmd c args@ runs a command in a process and returns stdout
 cmd :: String -- ^ command to run
     -> [String] -- ^ list of arguments
     -> IO String -- ^ stdout
 cmd c args = cmdStdIn c args ""
 
--- | 'cmd_ c args' runs command in a process, output goes to stdout and stderr
+-- | @cmd_ c args@ runs command in a process, output goes to stdout and stderr
 cmd_ :: String -> [String] -> IO ()
 cmd_ c args = do
   ret <- rawSystem c args
@@ -114,12 +116,12 @@
     ExitSuccess -> return True
     ExitFailure _ -> return False
 
--- | 'cmdBool c args' runs a command, and return Boolean status
+-- | @cmdBool c args@ runs a command, and return Boolean status
 cmdBool :: String -> [String] -> IO Bool
 cmdBool c args =
   boolWrapper (rawSystem c args)
 
--- | 'cmdMaybe c args' runs a command, maybe returning output if it succeeds
+-- | @cmdMaybe c args@ runs a command, maybe returning output if it succeeds
 cmdMaybe :: String -> [String] -> IO (Maybe String)
 cmdMaybe c args = do
   (ret, out, _err) <- readProcessWithExitCode c args ""
@@ -127,32 +129,33 @@
     ExitSuccess -> return $ Just $ removeTrailingNewline out
     ExitFailure _ -> return Nothing
 
--- | 'cmdLines c args' runs a command, and returns list of stdout lines
+-- | @cmdLines c args@ runs a command, and returns list of stdout lines
 --
 -- @since 0.1.1
 cmdLines :: String -> [String] -> IO [String]
 cmdLines c args = lines <$> cmd c args
 
--- | 'cmdStdIn c args inp' runs a command, passing input string as stdin, and returns stdout
+-- | @cmdStdIn c args inp@ runs a command, passing input string as stdin, and returns stdout
 cmdStdIn :: String -> [String] -> String -> IO String
 cmdStdIn c args inp = removeTrailingNewline <$> readProcess c args inp
 
--- | 'shell cs' runs a command string in a shell, and returns stdout
+-- | @shell cs@ runs a command string in a shell, and returns stdout
 shell :: String -> IO String
 shell cs = cmd "sh" ["-c", cs]
 
--- | 'shell_ cs' runs a command string in a shell, output goes to stdout
+-- | @shell_ cs@ runs a command string in a shell, output goes to stdout
 shell_ :: String -> IO ()
 shell_ cs = cmd_ "sh" ["-c", cs]
 
--- | 'shellBool cs' runs a command string in a shell, output goes to stdout
+-- | @shellBool cs@ runs a command string in a shell, output goes to stdout
 --
 -- @since 0.2.0
 shellBool :: String -> IO Bool
 shellBool cs =
   boolWrapper (rawSystem "sh" ["-c", cs])
 
--- | 'cmdLog c args' logs a command with a datestamp
+-- FIXME cmdLog_
+-- | @cmdLog c args@ logs a command with a datestamp
 --
 -- @since 0.1.4
 cmdLog :: String -> [String] -> IO ()
@@ -160,27 +163,27 @@
   logMsg $ unwords $ c:args
   cmd_ c args
 
--- | 'cmdlog' deprecated alias for 'cmdLog' (will be removed in 0.3)
+-- | @cmdlog@ deprecated alias for 'cmdLog' (will be removed in 0.3)
 cmdlog :: String -> [String] -> IO ()
 cmdlog = cmdLog
 
--- | 'logMsg msg' outputs message with a timestamp
+-- | @logMsg msg@ outputs message with a timestamp
 logMsg :: String -> IO ()
 logMsg msg = do
   date <- cmd "date" ["+%T"]
   putStrLn $ date +-+ msg
 
--- | 'cmdN c args' dry-runs a command: prints command to stdout - more used for debugging
+-- | @cmdN c args@ dry-runs a command: prints command to stdout - more used for debugging
 cmdN :: String -> [String] -> IO ()
 cmdN c args = putStrLn $ unwords $ c:args
 
--- | 'cmdStdErr c args' runs command in a process, returning stdout and stderr
+-- | @cmdStdErr c args@ runs command in a process, returning stdout and stderr
 cmdStdErr :: String -> [String] -> IO (String, String)
 cmdStdErr c args = do
   (_ret, out, err) <- readProcessWithExitCode c args ""
   return (removeTrailingNewline out, removeTrailingNewline err)
 
--- -- | 'cmdAssert msg c args' runs command, if it fails output msg as error'.
+-- -- | @cmdAssert msg c args@ runs command, if it fails output msg as error'.
 -- cmdAssert :: String -> String -> [String] -> IO ()
 -- cmdAssert msg c args = do
 --   ret <- rawSystem c args
@@ -188,7 +191,7 @@
 --     ExitSuccess -> return ()
 --     ExitFailure _ -> error' msg
 
--- | 'cmdQuiet c args' runs a command hiding stderr, if it succeeds returns stdout
+-- | @cmdQuiet c args@ runs a command hiding stderr, if it succeeds returns stdout
 cmdQuiet :: String -> [String] -> IO String
 cmdQuiet c args = do
   (ret, out, err) <- readProcessWithExitCode c args ""
@@ -196,7 +199,7 @@
     ExitSuccess -> return $removeTrailingNewline out
     ExitFailure n -> error' $ quoteCmd c args +-+ "failed with status" +-+ show n ++ "\n" ++ err
 
--- | 'cmdSilent c args' runs a command hiding stdout: stderr is only output if it fails.
+-- | @cmdSilent c args@ runs a command hiding stdout: stderr is only output if it fails.
 cmdSilent :: String -> [String] -> IO ()
 cmdSilent c args = do
   (ret, _, err) <- readProcessWithExitCode c args ""
@@ -204,13 +207,13 @@
     ExitSuccess -> return ()
     ExitFailure n -> error' $ quoteCmd c args +-+ "failed with status" +-+ show n ++ "\n" ++ err
 
--- | 'cmdIgnoreErr c args inp' runs a command with input, drops stderr, and return stdout
+-- | @cmdIgnoreErr c args inp@ runs a command with input, drops stderr, and return stdout
 cmdIgnoreErr :: String -> [String] -> String -> IO String
 cmdIgnoreErr c args input = do
-  (_exit, out, _err) <- readProcessWithExitCode c args input
+  (_ret, out, _err) <- readProcessWithExitCode c args input
   return out
 
--- | 'cmdTry_ c args' runs the command if available
+-- | @cmdTry_ c args@ runs the command if available
 --
 -- @since 0.2.1
 cmdTry_ :: String -> [String] -> IO ()
@@ -219,26 +222,38 @@
   when (isJust have) $
     cmd_ c args
 
--- | 'grep pat file' greps pattern in file, and returns list of matches
+-- | Redirect stderr to stdout, ie with interleaved output
 --
--- @since 0.1.2
+-- @since 0.2.2
+cmdStderrToStdout :: String -> [String] -> IO (ExitCode, String)
+cmdStderrToStdout c args = do
+  (_, Just hout, _, p) <- createProcess ((proc c args) {std_out = CreatePipe,
+                                                      std_err = UseHandle stdout})
+  ret <- waitForProcess p
+  out <- hGetContents hout
+  return (ret,out)
+
+-- | @grep pat file@ greps pattern in file, and returns list of matches
+--
+-- @since 0.1.2 (fixed not to error in 0.2.2)
 grep :: String -> FilePath -> IO [String]
-grep pat file =
-  cmdLines "grep" [pat, file]
+grep pat file = do
+  mres <- cmdMaybe "grep" [pat, file]
+  return $ maybe [] lines mres
 
--- | 'grep_ pat file' greps pattern in file and returns Boolean status
+-- | @grep_ pat file@ greps pattern in file and returns Boolean status
 grep_ :: String -- ^ pattern
       -> FilePath -- ^ file
       -> IO Bool -- ^ result
 grep_ pat file =
   cmdBool "grep" ["-q", pat, file]
 
--- | 'egrep_ pat file' greps extended regexp in file, and returns Boolean status
+-- | @egrep_ pat file@ greps extended regexp in file, and returns Boolean status
 egrep_ :: String -> FilePath -> IO Bool
 egrep_ pat file =
   cmdBool "grep" ["-q", "-e", pat, file]
 
--- | 'sudo c args' runs a command as sudo returning stdout
+-- | @sudo c args@ runs a command as sudo returning stdout
 --
 -- Result type changed from IO () to IO String in 0.2.0
 sudo :: String -- ^ command
@@ -246,7 +261,7 @@
      -> IO String
 sudo = sudoInternal cmd
 
--- | 'sudo_ c args' runs a command as sudo
+-- | @sudo_ c args@ runs a command as sudo
 --
 -- @since 0.2.0
 sudo_ :: String -- ^ command
@@ -278,24 +293,24 @@
 -- singleLine "" = ""
 -- singleLine s = (head . lines) s
 
--- | 'removePrefix prefix original' removes prefix from string if present
+-- | @removePrefix prefix original@ removes prefix from string if present
 removePrefix :: String -> String-> String
 removePrefix prefix orig =
   fromMaybe orig $ stripPrefix prefix orig
 
--- | 'removeStrictPrefix prefix original' removes prefix, or fails with error'
+-- | @removeStrictPrefix prefix original@ removes prefix, or fails with error'
 removeStrictPrefix :: String -> String -> String
 removeStrictPrefix prefix orig =
   fromMaybe (error' prefix +-+ "is not prefix of" +-+ orig) $ stripPrefix prefix orig
 
--- | 'removeSuffix suffix original' removes suffix from string if present
+-- | @removeSuffix suffix original@ removes suffix from string if present
 removeSuffix :: String -> String -> String
 removeSuffix suffix orig =
   fromMaybe orig $ stripSuffix suffix orig
   where
     stripSuffix sf str = reverse <$> stripPrefix (reverse sf) (reverse str)
 
--- | 'warning' outputs to stderr
+-- | @warning@ outputs to stderr
 --
 -- @since 0.2.0
 warning :: String -> IO ()
@@ -371,26 +386,12 @@
       void $ waitForProcess p1
       void $ waitForProcess p2
 
--- | Monadic if
--- @ifM test actTrue actFalse@
--- (taken from protolude)
---
--- @since 0.2.1
-ifM :: Monad m => m Bool -> m a -> m a -> m a
-ifM p x y = p >>= \b -> if b then x else y
-
--- | Monadic when
--- @whenM test action@
---
--- @since 0.2.1
-whenM :: Monad m => m Bool -> m () -> m ()
-whenM p x = p >>= \b -> when b x
-
 -- | Assert program in PATH
+--
 -- @needProgram progname@
 --
 -- @since 0.2.1
 needProgram :: String -> IO ()
 needProgram prog = do
   mx <- findExecutable prog
-  unless (isJust mx) $ error' $ "program needs " ++ prog
+  unless (isJust mx) $ error' $ "missing program: " ++ prog
diff --git a/SimpleCmd/Git.hs b/SimpleCmd/Git.hs
--- a/SimpleCmd/Git.hs
+++ b/SimpleCmd/Git.hs
@@ -5,50 +5,57 @@
 module SimpleCmd.Git (
   git,
   git_,
+  gitBool,
   gitBranch,
   gitDiffQuiet,
   grepGitConfig,
   isGitDir,
   rwGitDir) where
 
-import Data.List (isPrefixOf)
 import System.Directory (doesDirectoryExist)
 import System.FilePath ((</>))
 
-import SimpleCmd (cmd, cmd_, cmdBool, cmdLines, egrep_, removePrefix)
+import SimpleCmd (cmd, cmd_, cmdBool, egrep_)
 
 #if (defined(MIN_VERSION_base) && MIN_VERSION_base(4,8,0))
 #else
 import Control.Applicative ((<$>))
 #endif
 
--- | 'git c args' runs git command and return output
+-- | @git c args@ runs git command and return output
 git :: String -- ^ git command
     -> [String] -- ^ arguments
     -> IO String -- ^ output
 git c args =
   cmd "git" (c:args)
 
--- | 'git_ c args' run git command with output to stdout and stderr
+-- | @git_ c args@ run git command with output to stdout and stderr
 git_ :: String -> [String] -> IO ()
 git_ c args =
   cmd_ "git" (c:args)
 
--- | 'isGitDir dir' checks if directory has a .git/ subdir
+-- | @gitBool c args@ runs git command and return result
+gitBool :: String -- ^ git command
+        -> [String] -- ^ arguments
+        -> IO Bool -- ^ result
+gitBool c args =
+  cmdBool "git" (c:args)
+
+-- | @isGitDir dir@ checks if directory has a .git/ subdir
 isGitDir :: FilePath -> IO Bool
 isGitDir dir = doesDirectoryExist (dir </> ".git")
 
--- | 'gitBranch' returns the git branch of the current directory
+-- | @gitBranch@ returns the git branch of the current directory
 gitBranch :: IO String
 gitBranch =
-  removePrefix "* " . head . filter (isPrefixOf "* ") <$> cmdLines "git" ["branch"]
+  git "rev-parse" ["--abbrev-ref", "HEAD"]
 
--- | 'rwGitDir' checks if a git repo is under ssh
+-- | @rwGitDir@ checks if a git repo is under ssh
 rwGitDir :: IO Bool
 rwGitDir =
   grepGitConfig "url = \\(ssh://\\|git@\\)"
 
--- | 'grepGitConfig pat' greps ".git/config" for extended regexp
+-- | @grepGitConfig pat@ greps ".git/config" for extended regexp
 --
 -- @since 0.1.1
 grepGitConfig :: String -> IO Bool
@@ -58,7 +65,7 @@
     then egrep_ key ".git/config"
     else return False
 
--- | 'gitDiffQuiet' checks if unchanged
+-- | @gitDiffQuiet@ checks if unchanged
 --
 -- @since 0.1.3
 gitDiffQuiet :: [String] -> IO Bool
diff --git a/SimpleCmd/Rpm.hs b/SimpleCmd/Rpm.hs
--- a/SimpleCmd/Rpm.hs
+++ b/SimpleCmd/Rpm.hs
@@ -8,7 +8,7 @@
 
 import SimpleCmd (cmdLines)
 
--- | 'rpmspec args maybe-queryformat specfile' runs rpmspec query on file with optional args (a newline is appended to any queryformat)
+-- | @rpmspec args mqueryformat specfile@ runs rpmspec query on file with optional args (a newline is appended to any queryformat)
 --
 -- @since 0.1.1
 rpmspec :: [String] -> Maybe String -> FilePath -> IO [String]
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -1,2 +1,3 @@
 - source headers
 - examples
+- add cmdLog_ (api break)
diff --git a/simple-cmd.cabal b/simple-cmd.cabal
--- a/simple-cmd.cabal
+++ b/simple-cmd.cabal
@@ -1,9 +1,9 @@
 name:                simple-cmd
-version:             0.2.1
+version:             0.2.2
 synopsis:            Simple String-based process commands
 description:
             Simple wrappers over System.Process
-            (readProcess, readProcessWithExitCode, rawSystem, and createProcess). 
+            (readProcess, readProcessWithExitCode, rawSystem, and createProcess).
             The idea is to provide some common idioms for calling out to commands
             from programs.  For more advanced shell-scripting or streaming
             use turtle, shelly, command, etc.
@@ -11,14 +11,14 @@
 license-file:        LICENSE
 author:              Jens Petersen
 maintainer:          juhpetersen@gmail.com
-copyright:           Jens Petersen <juhpetersen@gmail.com>
+copyright:           2017-2020 Jens Petersen <juhpetersen@gmail.com>
 category:            System
 build-type:          Simple
 cabal-version:       >=1.10
 extra-source-files:  README.md ChangeLog.md TODO
 tested-with:         GHC == 7.4.2, GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3,
                      GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5,
-                     GHC == 8.8.1
+                     GHC == 8.8.3, GHC == 8.10.1
 
 source-repository head
   type:     git
@@ -30,6 +30,7 @@
                        SimpleCmd.Rpm
   build-depends:       base < 5,
                        directory,
+                       extra,
                        filepath,
                        process >= 1.4.3.0,
                        unix
