diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,64 +0,0 @@
-## Changelog
-
-#### Upcoming
-
-#### v0.2.0.2
-
-*Revisions*
-
-* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))
-
-#### v0.2.0.1
-
-*Revisions*
-
-* Fixed a bug where passing arguments would only work when running in the git directory. ([#57](https://github.com/hjwylde/git-fmt/issues/57))
-
-#### v0.2.0.0
-
-*Major*
-
-* Removed `--list-ugly` and `--dry-run` options. ([#29](https://github.com/hjwylde/git-fmt/issues/29))
-* Restricted use of `--quiet` and `--verbose` at the same time. ([#35](https://github.com/hjwylde/git-fmt/issues/35))
-* Updated project structure to delegate pretty printing to other binaries. ([#38](https://github.com/hjwylde/git-fmt/issues/38))
-* Added a `.omniyaml.yaml` config file. ([#38](https://github.com/hjwylde/git-fmt/issues/38))
-
-*Minor*
-
-* Added `--mode` option (either `normal` or `dry-run`). ([#29](https://github.com/hjwylde/git-fmt/issues/29))
-* Added `--null` option (use the null terminator as the delimiter for inputs). ([#27](https://github.com/hjwylde/git-fmt/issues/27))
-* Added support for directories as arguments (directories include all files within recursively). ([#30](https://github.com/hjwylde/git-fmt/issues/30))
-* Added parallelisation. ([#48](https://github.com/hjwylde/git-fmt/issues/48))
-
-*Revisions*
-
-* Added a warning for when files aren't found. ([#29](https://github.com/hjwylde/git-fmt/issues/29))
-* Updated internal use of `git ls-files` to use the null terminator option. ([#27](https://github.com/hjwylde/git-fmt/issues/27))
-* Fixed debug log messages to have timestamp and log level on all lines. ([#33](https://github.com/hjwylde/git-fmt/issues/33))
-* Tidied up error messages from git. ([#40](https://github.com/hjwylde/git-fmt/issues/40))
-* Tidied up error messages from parsing the config. ([#43](https://github.com/hjwylde/git-fmt/issues/43))
-
-#### v0.1.0.3
-
-*Revisions*
-
-* Fixed a bug where passing arguments would only work when running in the git directory. ([#57](https://github.com/hjwylde/git-fmt/issues/57))
-
-#### v0.1.0.2
-
-*Revisions*
-
-* Fixed a bug where UTF-8 characters in strings weren't printed properly. ([#26](https://github.com/hjwylde/git-fmt/issues/26))
-
-#### v0.1.0.1
-
-*Revisions*
-
-* Fixed a bug where integers were printed as rationals. ([#25](https://github.com/hjwylde/git-fmt/issues/25))
-
-#### v0.1.0.0
-
-This is first release of the `git-fmt` binary!
-It provides a basic syntax for formatting files in a git repository.
-Currently only JSON is supported.
-
diff --git a/README.md b/README.md
deleted file mode 100644
--- a/README.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# git-fmt
-
-[![Project Status: Wip - Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](http://www.repostatus.org/badges/1.0.0/wip.svg)](http://www.repostatus.org/#wip)
-[![Build Status](https://travis-ci.org/hjwylde/git-fmt.svg?branch=master)](https://travis-ci.org/hjwylde/git-fmt)
-[![Release](https://img.shields.io/github/release/hjwylde/git-fmt.svg)](https://github.com/hjwylde/git-fmt/releases/latest)
-
-(Side note: the formatting component of this project will eventually be split out and named omnifmt.)
-
-`git-fmt` adds a custom command to Git that automatically formats code by using external
-    pretty-printers.
-The idea was taken from [gofmt](https://golang.org/cmd/gofmt/), just with a bit of expansion to more
-    languages.
-
-Formatted code is:
-
-* Easier to write: never worry about minor formatting concerns while hacking away.
-* Easier to read: when all code looks the same you need not mentally convert others' formatting
-  style into something you can understand.
-* Easier to maintain: mechanical changes to the source don't cause unrelated changes to the file's
-  formatting; diffs show only the real changes.
-* Uncontroversial: never have a debate about spacing or brace position ever again.
-
-(Bullet points taken from https://blog.golang.org/go-fmt-your-code.)
-
-### Installing
-
-Installing `git-fmt` is easiest done using either
-    [stack](https://github.com/commercialhaskell/stack) (recommended) or
-    [Cabal](https://github.com/haskell/cabal).
-Alternatively you may download a pre-compiled binary of the
-    [latest release](https://github.com/hjwylde/git-fmt/releases/latest).
-
-**Using stack:**
-
-```bash
-stack install git-fmt
-export PATH=$PATH:~/.local/bin
-```
-
-**Using Cabal:**
-
-```bash
-cabal-install git-fmt
-export PATH=$PATH:~/.cabal/bin
-```
-
-### Usage
-
-`git-fmt` itself just provides a way to select files in the git repository and pipe them through a
-    pretty-printer.
-It also provides a dry-run mode that will show you which files need prettifying.
-
-#### Configuration
-
-Configuration is done through an `.omnifmt.yaml` file in the repository's top-level directory.
-
-To show how, here's an example `.omnifmt.yaml`:
-```yaml
-haskell:
-    extensions: ["hs", "lhs"]
-    command:    "stylish-haskell {{input}} > {{output}}"
-
-javascript:
-    extensions: ["js"]
-    command:    "js-beautify {{input}} > {{output}}"
-
-json:
-    extensions: ["json"]
-    command:    "json_pp < {{input}} > {{output}}"
-```
-
-That's all it takes!
-Each command must declare how to read the input file and how to write to the temporary file.
-The temporary file is used to compare whether the original input was pretty or ugly before writing
-    to the original.
-
-Extensions is pretty self explanatory, but if you use the same extension more than once then
-    precedence goes to the first defined pretty-printer.
-
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -18,14 +18,14 @@
 
 import Control.Monad
 import Control.Monad.Logger
-import Control.Monad.Parallel (MonadParallel(..))
+import Control.Monad.Parallel (MonadParallel (..))
 
-import              Data.List.Extra     (dropEnd, lower)
-import              Data.Text           (Text)
-import qualified    Data.Text           as T
-import qualified    Data.Text.Encoding  as T
-import qualified    Data.Text.IO        as T
-import              Data.Time           (getZonedTime, formatTime, defaultTimeLocale)
+import           Data.List.Extra    (dropEnd, lower)
+import           Data.Text          (Text)
+import qualified Data.Text          as T
+import qualified Data.Text.Encoding as T
+import qualified Data.Text.IO       as T
+import           Data.Time          (defaultTimeLocale, formatTime, getZonedTime)
 
 import Git.Fmt
 import Git.Fmt.Options.Applicative.Parser
diff --git a/git-fmt.cabal b/git-fmt.cabal
--- a/git-fmt.cabal
+++ b/git-fmt.cabal
@@ -1,5 +1,5 @@
 name:           git-fmt
-version:        0.2.0.2
+version:        0.2.1.0
 
 author:         Henry J. Wylde
 maintainer:     public@hjwylde.com
@@ -17,8 +17,6 @@
 category:       Development
 build-type:     Simple
 
-extra-source-files: CHANGELOG.md README.md
-
 source-repository head
     type:       git
     location:   git@github.com:hjwylde/git-fmt
@@ -51,6 +49,7 @@
         Git.Fmt.Process,
         Git.Fmt.Version,
         Paths_git_fmt,
+        System.Directory.Extra'
         System.IO.Extra'
 
     default-language: Haskell2010
diff --git a/src/Git/Fmt.hs b/src/Git/Fmt.hs
--- a/src/Git/Fmt.hs
+++ b/src/Git/Fmt.hs
@@ -29,23 +29,22 @@
 import qualified    Control.Monad.Parallel  as Parallel
 import              Control.Monad.Reader
 
-import              Data.List.Extra     (chunksOf, linesBy, lower, nub, replace)
+import              Data.List.Extra     (chunksOf, linesBy, lower, nub)
 import qualified    Data.Text           as T
 import              Data.Yaml           (prettyPrintParseException)
 import              Data.Yaml.Include   (decodeFileEither)
 
-import Git.Fmt.Config   as Config
+import Git.Fmt.Config as Config
 import Git.Fmt.Process
-import GHC.IO.Exception (IOErrorType(..))
 
 import Prelude hiding (read)
 
-import System.Directory.Extra hiding (withCurrentDirectory)
+import System.Directory.Extra   hiding (withCurrentDirectory)
+import System.Directory.Extra'
 import System.Exit
 import System.FilePath
-import System.IO.Error
-import System.IO.Extra'
 import System.IO.Temp
+import System.IO.Extra'
 
 -- | Options.
 data Options = Options {
@@ -66,18 +65,17 @@
 
 -- | Builds the files according to the options.
 handle :: (MonadIO m, MonadLogger m, MonadMask m, MonadParallel m) => Options -> m ()
-handle options = do
-    gitDir      <- findGitDirectory
-    filePaths   <- fmap (nub . concat) $ paths gitDir >>= mapM
+handle options = findTopLevelGitDirectory >>= \dir -> withCurrentDirectory (init dir) $ do
+    filePaths <- fmap (nub . concat) $ paths >>= mapM
         (\path -> ifM (liftIO $ doesDirectoryExist path)
             (liftIO $ listFilesRecursive path)
             (return [path])
             )
 
-    unlessM (liftIO . doesFileExist $ gitDir </> Config.fileName) $ panic (gitDir </> Config.fileName ++ ": not found")
+    unlessM (liftIO $ doesFileExist Config.defaultFileName) $ panic (Config.defaultFileName ++ ": not found")
 
-    config <- liftIO (decodeFileEither $ gitDir </> Config.fileName) >>= \ethr -> case ethr of
-        Left error      -> panic $ gitDir </> Config.fileName ++ ": error\n" ++ prettyPrintParseException error
+    config <- liftIO (decodeFileEither Config.defaultFileName) >>= \ethr -> case ethr of
+        Left error      -> panic $ Config.defaultFileName ++ ": error\n" ++ prettyPrintParseException error
         Right config    -> return config
 
     let supportedFilePaths = filter (supported config . T.pack . drop 1 . lower . takeExtension) filePaths
@@ -87,8 +85,8 @@
             (fmt options filePath (tmpDir </> filePath))
             ($(logWarn) $ T.pack (filePath ++ ": not found"))
     where
-        paths gitDir
-            | null (argPaths options)   = linesBy (== '\0') <$> runProcess_ "git" ["ls-files", "-z", gitDir]
+        paths
+            | null (argPaths options)   = linesBy (== '\0') <$> runProcess_ "git" ["ls-files", "-z"]
             | optNull options           = return $ concatMap (linesBy (== '\0')) (argPaths options)
             | otherwise                 = return $ argPaths options
         nChunks n xs = chunksOf (maximum [1, length xs `div` n]) xs
@@ -120,29 +118,32 @@
 normal filePath tmpFilePath = do
     $(logInfo) $ T.pack (filePath ++ ": prettified")
 
-    -- Try move the file, but if it's across a filesystem boundary then we may need to copy instead
-    liftIO $ renameFile tmpFilePath filePath `catchIOError` \e ->
-        if ioeGetErrorType e == UnsupportedOperation
-            then copyFile tmpFilePath filePath >> removeFile tmpFilePath
-            else ioError e
+    liftIO $ renameFile tmpFilePath filePath
 
 dryRun :: (MonadIO m, MonadLogger m) => FilePath -> FilePath -> m ()
 dryRun filePath _ = $(logInfo) $ T.pack (filePath ++ ": ugly")
 
-findGitDirectory :: (MonadIO m, MonadLogger m) => m String
-findGitDirectory = do
+findTopLevelGitDirectory :: (MonadIO m, MonadLogger m) => m String
+findTopLevelGitDirectory = do
     (exitCode, stdout, _) <- runProcess "git" ["rev-parse", "--show-toplevel"]
 
     if exitCode == ExitSuccess
-        then return $ init stdout
+        then return stdout
         else panic ".git/: not found"
 
 runProgram :: (MonadIO m, MonadLogger m) => Program -> FilePath -> FilePath -> m (ExitCode, String, String)
 runProgram program inputFilePath tmpFilePath = do
     liftIO $ createDirectoryIfMissing True (takeDirectory tmpFilePath)
 
-    runCommand $ foldr (uncurry replace) (T.unpack $ command program) [
-        ("{{input}}", inputFilePath),
-        ("{{output}}", tmpFilePath)
+    runCommand . T.unpack $ substitute (T.concat [command program, inputSuffix, outputSuffix]) [
+        (inputVariableName, T.pack $ '"':inputFilePath ++ "\""),
+        (outputVariableName, T.pack $ '"':tmpFilePath ++ "\"")
         ]
+    where
+        inputSuffix
+            | usesInputVariable (command program)   = T.empty
+            | otherwise                             = T.pack " < " `T.append` inputVariableName
+        outputSuffix
+            | usesOutputVariable (command program)  = T.empty
+            | otherwise                             = T.pack " > " `T.append` outputVariableName
 
diff --git a/src/Git/Fmt/Config.hs b/src/Git/Fmt/Config.hs
--- a/src/Git/Fmt/Config.hs
+++ b/src/Git/Fmt/Config.hs
@@ -15,21 +15,22 @@
 module Git.Fmt.Config (
     -- * Config
     Config(..),
-    emptyConfig,
+    emptyConfig, programFor, unsafeProgramFor, supported,
 
     -- * Program
     Program(..),
-    emptyProgram, programFor, unsafeProgramFor, supported,
+    emptyProgram, substitute, usesInputVariable, usesOutputVariable, inputVariableName,
+    outputVariableName,
 
     -- * Helper functions
-    fileName,
+    defaultFileName,
 ) where
 
 import Data.Aeson.Types
-import Data.HashMap.Lazy    (toList)
-import Data.List            (find)
-import Data.Maybe           (fromJust, isJust)
-import Data.Text            (Text)
+import Data.HashMap.Lazy (toList)
+import Data.List.Extra   (find)
+import Data.Maybe        (fromJust, isJust)
+import Data.Text         (Text, isInfixOf, replace)
 
 -- | A list of programs.
 data Config = Config {
@@ -47,12 +48,24 @@
 emptyConfig :: Config
 emptyConfig = Config []
 
+-- | Attempts to find a program for the given extension.
+programFor :: Config -> Text -> Maybe Program
+programFor config ext = find (\program -> ext `elem` extensions program) (programs config)
+
+-- | Finds a program for the given extension or errors.
+unsafeProgramFor :: Config -> Text -> Program
+unsafeProgramFor config = fromJust . programFor config
+
+-- | Checks if the given extension is supported (e.g., there is a program for it).
+supported :: Config -> Text -> Bool
+supported config = isJust . programFor config
+
 -- | A program has a semantic name, associated extensions and command.
 --   The command string may contain variables to be replaced by surrounding them with '{{..}}'.
 data Program = Program {
-    name        :: Text,
-    extensions  :: [Text],
-    command     :: Text
+    name       :: Text,
+    extensions :: [Text],
+    command    :: Text
     }
     deriving (Eq, Show)
 
@@ -64,19 +77,27 @@
 emptyProgram :: Program
 emptyProgram = Program "" [] "false"
 
--- | Attempts to find a program for the given extension.
-programFor :: Config -> Text -> Maybe Program
-programFor config ext = find (\program -> ext `elem` extensions program) (programs config)
+-- | Substitutes the mapping throughout the command.
+substitute :: Text -> [(Text, Text)] -> Text
+substitute = foldr (uncurry replace)
 
--- | Finds a program for the given extension or errors.
-unsafeProgramFor :: Config -> Text -> Program
-unsafeProgramFor config = fromJust . programFor config
+-- | Checks whether the text uses the input variable.
+usesInputVariable :: Text -> Bool
+usesInputVariable = isInfixOf inputVariableName
 
--- | Checks if the given extension is supported (e.g., there is a program for it).
-supported :: Config -> Text -> Bool
-supported config = isJust . programFor config
+-- | Checks whether the text uses the output variable.
+usesOutputVariable :: Text -> Bool
+usesOutputVariable = isInfixOf outputVariableName
 
--- | The file name of the default config file.
-fileName :: String
-fileName = ".omnifmt.yaml"
+-- | The input variable name.
+inputVariableName :: Text
+inputVariableName = "{{input}}"
+
+-- | The output variable name.
+outputVariableName :: Text
+outputVariableName = "{{output}}"
+
+-- | The file name of the default config.
+defaultFileName :: FilePath
+defaultFileName = ".omnifmt.yaml"
 
diff --git a/src/Git/Fmt/Process.hs b/src/Git/Fmt/Process.hs
--- a/src/Git/Fmt/Process.hs
+++ b/src/Git/Fmt/Process.hs
@@ -22,10 +22,10 @@
 
 import Data.Text hiding (unwords)
 
-import              System.Exit
-import              System.IO.Extra'
-import              System.Process (CreateProcess, CmdSpec(..))
-import qualified    System.Process as System
+import           System.Exit
+import           System.IO.Extra'
+import           System.Process   (CmdSpec (..), CreateProcess)
+import qualified System.Process   as System
 
 -- | Runs the given executable with the arguments.
 --   Returns the exit code, stdout and stderr.
diff --git a/src/System/Directory/Extra'.hs b/src/System/Directory/Extra'.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Directory/Extra'.hs
@@ -0,0 +1,27 @@
+
+{-|
+Module      : System.Directory.Extra'
+Description : Extra extra directory utilities.
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+
+Extra extra directory utilities.
+-}
+
+module System.Directory.Extra' (
+    -- * Changing directories
+    withCurrentDirectory,
+) where
+
+import Control.Monad.Catch    (MonadMask, bracket)
+import Control.Monad.IO.Class
+
+import System.Directory
+
+-- | @withCurrentDirectory dir action@ performs @action@ with the current directory set to @dir@.
+--   The current directory is reset back to what it was afterwards.
+withCurrentDirectory :: (MonadIO m, MonadMask m) => FilePath -> m a -> m a
+withCurrentDirectory dir action = bracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ \_ -> liftIO (setCurrentDirectory dir) >> action
+
