diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,82 +0,0 @@
-## Changelog
-
-#### Upcoming
-
-#### v0.2.1.2
-
-*Revisions*
-
-* Fixed a bug causing prettifying to fail across filesystem boundaries. ([#72](https://github.com/hjwylde/git-fmt/issues/72))
-
-#### v0.2.1.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.1.0
-
-*Minor*
-
-* Added default use of stdin and stdout when variables not specified in a program command. ([#49](https://github.com/hjwylde/git-fmt/issues/49))
-
-#### 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,94 +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` with 4 programs:
-```yaml
-haskell:
-    extensions: ["hs", "lhs"]
-    command:    "stylish-haskell {{input}} > {{output}}"
-
-javascript:
-    extensions: ["js"]
-    command:    "js-beautify -"
-
-json:
-    extensions: ["json"]
-    command:    "json_pp"
-
-ruby:
-    extensions: ["rb"]
-    command:    "ruby-beautify {{input}}"
-```
-
-That's all it takes!
-Each command declares how to read the input file and how to write to the output file.
-If the input variable is omitted, it is fed to the command through `stdin`.
-Likewise if the output variable is omitted it is read from `stdout`.
-The output 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.
-
-#### Examples
-
-See the [examples/](https://github.com/hjwylde/git-fmt/tree/master/examples/) directory for some
-    common pretty-printers and their corresponding omnifmt config.
-Just don't forget to actually call the config file `.omnifmt.yaml`!
-
-**NB:** Be careful when using these examples, I have not tested the pretty-printers fully to check
-    that they will not destroy your code by accident.
-
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.1.2
+version:        0.2.2.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
@@ -26,6 +24,7 @@
 executable git-fmt
     main-is:        Main.hs
     hs-source-dirs: app/
+    ghc-options:    -threaded -with-rtsopts=-N
 
     default-language: Haskell2010
     other-extensions:
@@ -48,18 +47,19 @@
         Git.Fmt.Options.Applicative.Parser
     other-modules:
         Git.Fmt.Config,
+        Git.Fmt.Exit,
         Git.Fmt.Process,
         Git.Fmt.Version,
-        Paths_git_fmt,
-        System.IO.Extra'
+        Paths_git_fmt
 
     default-language: Haskell2010
     other-extensions:
         FlexibleContexts
+        MultiParamTypeClasses
         OverloadedStrings
         TemplateHaskell
     build-depends:
-        aeson == 0.8.*,
+        aeson >= 0.8 && < 0.10,
         base == 4.8.*,
         directory == 1.2.*,
         exceptions == 0.8.*,
diff --git a/src/Git/Fmt.hs b/src/Git/Fmt.hs
--- a/src/Git/Fmt.hs
+++ b/src/Git/Fmt.hs
@@ -10,8 +10,9 @@
 Options and handler for the git-fmt command.
 -}
 
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE FlexibleContexts      #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE TemplateHaskell       #-}
 
 module Git.Fmt (
     -- * Options
@@ -21,38 +22,40 @@
     handle,
 ) where
 
-import              Control.Monad.Catch     (MonadMask)
-import              Control.Monad.Extra
-import              Control.Monad.IO.Class
-import              Control.Monad.Logger
-import              Control.Monad.Parallel  (MonadParallel)
-import qualified    Control.Monad.Parallel  as Parallel
-import              Control.Monad.Reader
+import           Control.Applicative
+import           Control.Concurrent
+import           Control.Monad.Catch    (MonadMask)
+import           Control.Monad.Extra
+import           Control.Monad.IO.Class
+import           Control.Monad.Logger
+import           Control.Monad.Parallel (MonadParallel)
+import qualified Control.Monad.Parallel as Parallel
+import           Control.Monad.Reader
 
-import              Data.List.Extra     (chunksOf, linesBy, lower, nub)
-import qualified    Data.Text           as T
-import              Data.Yaml           (prettyPrintParseException)
-import              Data.Yaml.Include   (decodeFileEither)
+import           Data.List.Extra   (chunksOf, linesBy, lower, nub)
+import           Data.Maybe        (fromMaybe)
+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.Exit
 import Git.Fmt.Process
-import GHC.IO.Exception (IOErrorType(..))
 
 import Prelude hiding (read)
 
 import System.Directory.Extra hiding (withCurrentDirectory)
 import System.Exit
 import System.FilePath
-import System.IO.Error
-import System.IO.Extra'
 import System.IO.Temp
 
 -- | Options.
 data Options = Options {
-        optChatty   :: Chatty,
-        optNull     :: Bool,
-        optMode     :: Mode,
-        argPaths    :: [FilePath]
+        optChatty     :: Chatty,
+        optNull       :: Bool,
+        optNumThreads :: Maybe Int,
+        optMode       :: Mode,
+        argPaths      :: [FilePath]
     }
     deriving (Eq, Show)
 
@@ -73,6 +76,8 @@
             (liftIO $ listFilesRecursive path)
             (return [path])
             )
+    numThreads  <- liftIO getNumCapabilities >>= \numCapabilities ->
+        return $ fromMaybe numCapabilities (optNumThreads options)
 
     unlessM (liftIO . doesFileExist $ gitDir </> Config.defaultFileName) $ panic (gitDir </> Config.defaultFileName ++ ": not found")
 
@@ -83,7 +88,7 @@
     let supportedFilePaths = filter (supported config . T.pack . drop 1 . lower . takeExtension) filePaths
 
     flip runReaderT config . withSystemTempDirectory "git-fmt" $ \tmpDir ->
-        Parallel.sequence_ . map sequence . nChunks 8 . flip map supportedFilePaths $ \filePath -> ifM (liftIO $ doesFileExist filePath)
+        Parallel.sequence_ . map sequence . nChunks numThreads . flip map supportedFilePaths $ \filePath -> ifM (liftIO $ doesFileExist filePath)
             (fmt options filePath (tmpDir </> filePath))
             ($(logWarn) $ T.pack (filePath ++ ": not found"))
     where
@@ -120,11 +125,7 @@
 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")
@@ -138,12 +139,12 @@
         else panic ".git/: not found"
 
 runProgram :: (MonadIO m, MonadLogger m) => Program -> FilePath -> FilePath -> m (ExitCode, String, String)
-runProgram program inputFilePath tmpFilePath = do
+runProgram program filePath tmpFilePath = do
     liftIO $ createDirectoryIfMissing True (takeDirectory tmpFilePath)
 
     runCommand . T.unpack $ substitute (T.concat [command program, inputSuffix, outputSuffix]) [
-        (inputVariableName, T.pack $ '"':inputFilePath ++ "\""),
-        (outputVariableName, T.pack $ '"':tmpFilePath ++ "\"")
+        (inputVariableName, quote filePath),
+        (outputVariableName, quote tmpFilePath)
         ]
     where
         inputSuffix
@@ -152,4 +153,8 @@
         outputSuffix
             | usesOutputVariable (command program)  = T.empty
             | otherwise                             = T.pack " > " `T.append` outputVariableName
+        quote str   = T.pack $ '"':concatMap escape str ++ "\""
+        escape '\\' = "\\\\"
+        escape '"'  = "\\\""
+        escape c    = [c]
 
diff --git a/src/Git/Fmt/Exit.hs b/src/Git/Fmt/Exit.hs
new file mode 100644
--- /dev/null
+++ b/src/Git/Fmt/Exit.hs
@@ -0,0 +1,39 @@
+
+{-|
+Module      : Git.Fmt.Exit
+Description : Extra exit utilities.
+
+Copyright   : (c) Henry J. Wylde, 2015
+License     : BSD3
+Maintainer  : public@hjwylde.com
+
+Extra exit utilities.
+-}
+
+{-# LANGUAGE TemplateHaskell #-}
+
+module Git.Fmt.Exit (
+    -- * Exiting
+    panicWith, panic, exitFast,
+) where
+
+import Control.Monad.IO.Class
+import Control.Monad.Logger
+
+import Data.Text (pack)
+
+import System.Exit
+
+-- | Panics, logging the error to stderr and exiting fast with the code.
+panicWith :: (MonadIO m, MonadLogger m) => String -> Int -> m a
+panicWith error code = $(logError) (pack error) >> exitFast code
+
+-- | Panics, logging the error to stderr and exiting fast with 128.
+panic :: (MonadIO m, MonadLogger m) => String -> m a
+panic error = panicWith error 128
+
+-- | Exits fast with the given code (may be 0 for success!).
+exitFast :: (MonadIO m) => Int -> m a
+exitFast 0 = liftIO exitSuccess
+exitFast code = liftIO $ exitWith (ExitFailure code)
+
diff --git a/src/Git/Fmt/Options/Applicative/Parser.hs b/src/Git/Fmt/Options/Applicative/Parser.hs
--- a/src/Git/Fmt/Options/Applicative/Parser.hs
+++ b/src/Git/Fmt/Options/Applicative/Parser.hs
@@ -15,6 +15,7 @@
     gitFmtPrefs, gitFmtInfo, gitFmt,
 ) where
 
+import Data.Char    (isDigit)
 import Data.Version (showVersion)
 
 import Options.Applicative
@@ -59,6 +60,11 @@
         long "null", short '0',
         help "Input files are delimited by a null terminator instead of white space"
         ])
+    <*> natOption (mconcat [
+        long "threads", metavar "INT",
+        value Nothing, showDefaultWith $ const "number of processors",
+        help "Specify the number of threads to use"
+        ])
     <*> modeOption (mconcat [
         long "mode", short 'm', metavar "MODE",
         value Normal, showDefaultWith $ const "normal",
@@ -68,7 +74,10 @@
         metavar "-- PATHS..."
         ])
     where
-        modeOption = option $ readerAsk >>= \opt -> case opt of
+        natOption   = option $ readerAsk >>= \opt -> if all isDigit opt
+            then return $ Just (read opt :: Int)
+            else readerError $ "not a natural number `" ++ opt ++ "'"
+        modeOption  = option $ readerAsk >>= \opt -> case opt of
             "normal"    -> return Normal
             "dry-run"   -> return DryRun
             _           -> readerError $ "unrecognised mode `" ++ opt ++ "'"
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,11 @@
 
 import Data.Text hiding (unwords)
 
+import Git.Fmt.Exit
+
 import           System.Exit
-import           System.IO.Extra'
-import           System.Process   (CmdSpec (..), CreateProcess)
-import qualified System.Process   as System
+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/IO/Extra'.hs b/src/System/IO/Extra'.hs
deleted file mode 100644
--- a/src/System/IO/Extra'.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-
-{-|
-Module      : System.IO.Extra'
-Description : Extra extra IO utilities.
-
-Copyright   : (c) Henry J. Wylde, 2015
-License     : BSD3
-Maintainer  : public@hjwylde.com
-
-Extra extra IO utilities.
--}
-
-{-# LANGUAGE TemplateHaskell #-}
-
-module System.IO.Extra' (
-    -- * Exiting
-    panicWith, panic, exitFast,
-) where
-
-import Control.Monad.IO.Class
-import Control.Monad.Logger
-
-import Data.Text (pack)
-
-import System.Exit
-
--- | Panics, logging the error to stderr and exiting fast with the code.
-panicWith :: (MonadIO m, MonadLogger m) => String -> Int -> m a
-panicWith error code = $(logError) (pack error) >> exitFast code
-
--- | Panics, logging the error to stderr and exiting fast with 128.
-panic :: (MonadIO m, MonadLogger m) => String -> m a
-panic error = panicWith error 128
-
--- | Exits fast with the given code (may be 0 for success!).
-exitFast :: (MonadIO m) => Int -> m a
-exitFast 0 = liftIO exitSuccess
-exitFast code = liftIO $ exitWith (ExitFailure code)
-
