diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2003-2009, John Wiegley.  All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+- Neither the name of New Artisans LLC nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,285 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+{-# LANGUAGE FlexibleContexts #-}
+
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+
+module Main where
+
+import           Control.Concurrent (threadDelay)
+import           Control.Monad
+import           Control.Monad.IO.Class (MonadIO(..))
+import qualified Data.ByteString as B (readFile)
+import           Data.Function (fix)
+import           Data.Foldable (foldlM)
+import           Data.Map (Map)
+import qualified Data.Map as Map
+import           Data.Maybe
+import           Data.Text (Text)
+import qualified Data.Text as T (pack, unpack)
+import qualified Data.Text.Lazy as TL (pack, unpack, toStrict, init)
+import           Data.Time
+import           Filesystem (getModified, isDirectory, isFile, canonicalizePath)
+import           Filesystem.Path.CurrentOS (FilePath, (</>), parent, null)
+import           Git hiding (Options)
+import           Git.Libgit2 (lgFactory, withLibGitDo)
+import           Git.Utils (treeBlobEntries)
+import           Options.Applicative
+import           Prelude hiding (FilePath, null)
+import           Shelly (toTextIgnore, fromText, silently, shelly, run)
+import           System.IO (stderr)
+import           System.Locale (defaultTimeLocale)
+import           System.Log.Formatter (tfLogFormatter)
+import           System.Log.Handler (setFormatter)
+import           System.Log.Handler.Simple (streamHandler)
+import           System.Log.Logger
+
+instance Read FilePath
+
+data Options = Options
+    { verbose    :: Bool
+    , debug      :: Bool
+    , gitDir     :: String
+    , workingDir :: String
+    , interval   :: Int
+    , resume     :: Bool
+    }
+
+options :: Parser Options
+options = Options
+    <$> switch (short 'v' <> long "verbose" <> help "Display info")
+    <*> switch (short 'D' <> long "debug" <> help "Display debug")
+    <*> strOption
+        (long "git-dir" <> value ".git"
+         <> help "Git repository to store snapshots in (def: \".git\")")
+    <*> strOption (short 'd' <> long "work-dir" <> value ""
+                   <> help "The working tree to snapshot (def: \".\")")
+    <*> option (short 'i' <> long "interval" <> value 60
+                <> help "Snapshot each N seconds")
+    <*> switch (short 'r' <> long "resume" <> value False
+                <> help "Resumes using last set of snapshots")
+
+main :: IO ()
+main = withLibGitDo $ execParser opts >>= doMain
+  where
+    opts = info (helper <*> options)
+                (fullDesc <> progDesc desc <> header hdr)
+    hdr  = "git-monitor 1.0.0 - quickly snapshot working tree changes"
+    desc = "\nPassively snapshot working tree changes efficiently.\n\n\
+           \The intended usage is to run \"git monitor &\" in your project\n\
+           \directory before you begin a hacking session.\n\n\
+           \Snapshots are kept in refs/snapshots/refs/heads/$BRANCH"
+
+doMain :: Options -> IO ()
+doMain opts = do
+    -- Setup logging service if --verbose is used
+    when (verbose opts) $ initLogging (debug opts)
+
+    -- Ask Git for the user name and email in this repository
+    (userName,userEmail) <- shelly $ silently $
+        (,) <$> (TL.init <$> run "git" ["config", "user.name"])
+            <*> (TL.init <$> run "git" ["config", "user.email"])
+
+    let gDir = fromText (TL.pack (gitDir opts))
+    isDir <- isDirectory gDir
+    gd    <- if isDir
+             then return gDir
+             else shelly $ silently $
+                  fromText . TL.init <$> run "git" ["rev-parse", "--git-dir"]
+
+    let wDir = fromText (TL.pack (workingDir opts))
+        wd   = if null wDir then parent gd else wDir
+
+    -- Make sure we're in a known branch, and if so, let it begin
+    forever $ withRepository lgFactory gd $ do
+        infoL $ "Saving snapshots under " ++ fileStr gd
+        infoL $ "Working tree: " ++ fileStr wd
+        ref <- lookupRef "HEAD"
+        void $ case ref of
+            Just (Reference _ (RefSymbolic name)) -> do
+                infoL $ "Tracking branch " ++ T.unpack name
+                start wd (TL.toStrict userName) (TL.toStrict userEmail) name
+            _ -> do
+                infoL "Cannot use git-monitor if no branch is checked out"
+                liftIO $ threadDelay (interval opts * 1000000)
+  where
+    initLogging debugMode = do
+        let level | debugMode    = DEBUG
+                  | verbose opts = INFO
+                  | otherwise    = NOTICE
+        h <- (`setFormatter` tfLogFormatter "%H:%M:%S" "$time - [$prio] $msg")
+             <$> streamHandler System.IO.stderr level
+        removeAllHandlers
+        updateGlobalLogger "git-monitor" (setLevel level)
+        updateGlobalLogger "git-monitor" (addHandler h)
+
+    start wd userName userEmail ref = do
+        let sref = "refs/snapshots/" <> ref
+
+        -- Usually we ignore any snapshot history from a prior invocation of
+        -- git-monitor, leaving those objects for git gc to cleanup; however,
+        -- if --resume, continue the snapshot history from where we last left
+        -- off.  Note that if --resume is always used, this can end up
+        -- consuming a lot of disk space over time.
+        --
+        -- Also, it is useful to root the snapshot history at the current HEAD
+        -- commit.  Note that it must be a Just value at this point, see
+        -- above.
+        scr  <- if resume opts
+                then resolveRef sref
+                else return Nothing
+        scr' <- maybe (fromJust <$> resolveRef "HEAD") return scr
+        sc   <- resolveCommitRef scr'
+        str  <- resolveTreeRef (commitTree sc)
+        toid <- writeTree str
+        ft   <- readFileTree' str wd (isNothing scr)
+
+        -- Begin the snapshotting process, which continues indefinitely until
+        -- the process is stopped.  It is safe to cancel this process at any
+        -- time, typically using SIGINT (C-c) or even SIGKILL.
+        snapshotTree opts wd userName userEmail ref sref sc str toid ft
+
+-- | 'snapshotTree' is the core workhorse of this utility.  It periodically
+--   checks the filesystem for changes to Git-tracked files, and snapshots
+--   any changes that have occurred in them.
+snapshotTree :: (Repository m, MonadIO m)
+             => Options -> FilePath
+             -> Text -> Text -> Text -> Text
+             -> Commit m -> Tree m -> TreeOid m
+             -> Map FilePath (FileEntry m)
+             -> m ()
+snapshotTree opts wd name email ref sref = fix $ \loop sc str toid ft -> do
+    -- Read the current working tree's state on disk
+    ft' <- readFileTree ref wd False
+
+    -- Prune files which have been removed since the last interval, and find
+    -- files which have been added or changed
+    Map.foldlWithKey' (\a p e -> a >> scanOldEntry str ft' p e) (return ()) ft
+    Map.foldlWithKey' (\a p e -> a >> scanNewEntry str ft p e) (return ()) ft'
+
+    -- If the snapshot tree changed, create a new commit to reflect it
+    toid' <- writeTree str
+    sc'   <- if toid /= toid'
+            then do
+                now <- liftIO getZonedTime
+                let sig = Signature
+                          { signatureName  = name
+                          , signatureEmail = email
+                          , signatureWhen  = now
+                          }
+                    msg = "Snapshot at "
+                       ++ formatTime defaultTimeLocale "%F %T %Z" now
+
+                c <- createCommit [commitRef sc] (treeRef str)
+                                  sig sig (T.pack msg) (Just sref)
+                infoL $ "Commit "
+                     ++ (T.unpack . renderObjOid . commitOid $ c)
+                return c
+            else return sc
+
+    -- Wait a given number of seconds
+    liftIO $ threadDelay (interval opts * 1000000)
+
+    -- Rinse, wash, repeat.
+    ref' <- lookupRef "HEAD"
+    let curRef = case ref' of
+            Just (Reference _ (RefSymbolic ref'')) -> ref''
+            _ -> ""
+    if ref /= curRef
+        then infoL $ "Branch changed to " ++ T.unpack curRef
+                  ++ ", restarting"
+        else loop sc' str toid' ft'
+
+  where
+    scanOldEntry :: (Repository m, MonadIO m)
+                 => Tree m
+                 -> Map FilePath (FileEntry m)
+                 -> FilePath -> FileEntry m -> m ()
+    scanOldEntry str ft fp _ = case Map.lookup fp ft of
+        Nothing -> do
+            infoL $ "Removed: " ++ fileStr fp
+            dropFromTree str fp
+        _ -> return ()
+
+    scanNewEntry :: (Repository m, MonadIO m)
+                 => Tree m
+                 -> Map FilePath (FileEntry m)
+                 -> FilePath -> FileEntry m -> m ()
+    scanNewEntry str ft fp (FileEntry mt (BlobEntry oid exe) _) =
+        case Map.lookup fp ft of
+            Nothing -> do
+                infoL $ "Added to snapshot: " ++ fileStr fp
+                putBlob' str fp oid exe
+            Just (FileEntry oldMt (BlobEntry oldOid oldExe) fileOid)
+                | oid /= oldOid || exe /= oldExe -> do
+                    infoL $ "Changed: " ++ fileStr fp
+                    putBlob' str fp oid exe
+                | mt /= oldMt || oid /= fileOid -> do
+                    path <- fileStr <$>
+                            liftIO (canonicalizePath (wd </> fp))
+                    infoL $ "Changed: " ++ fileStr fp
+                    contents <- liftIO $ B.readFile path
+                    newOid   <- createBlob (BlobString contents)
+                    putBlob' str fp newOid exe
+                | otherwise -> return ()
+            _ -> return ()
+    scanNewEntry _str _ft _fp (FileEntry _mt _ _foid) = return ()
+
+data FileEntry m = FileEntry
+    { fileModTime   :: UTCTime
+    , fileBlobEntry :: TreeEntry m
+    , fileChecksum  :: BlobOid m
+    }
+
+type FileTree m = Map FilePath (FileEntry m)
+
+readFileTree :: (Repository m, MonadIO m)
+             => Text -> FilePath -> Bool -> m (FileTree m)
+readFileTree ref wdir getHash = do
+    h <- resolveRef ref
+    case h of
+        Nothing -> pure Map.empty
+        Just h' -> do
+            tr <- resolveTreeRef . commitTree =<< resolveCommitRef h'
+            readFileTree' tr wdir getHash
+
+readFileTree' :: (Repository m, MonadIO m)
+              => Tree m -> FilePath -> Bool -> m (FileTree m)
+readFileTree' tr wdir getHash = do
+    blobs <- treeBlobEntries tr
+    foldlM (\m (fp,ent) -> do
+                 fent <- readModTime wdir getHash fp ent
+                 return $ maybe m (flip (Map.insert fp) m) fent)
+           Map.empty blobs
+
+readModTime :: (Repository m, MonadIO m)
+            => FilePath -> Bool -> FilePath -> TreeEntry m
+            -> m (Maybe (FileEntry m))
+readModTime wdir getHash fp ent = do
+    let path = wdir </> fp
+    debugL $ "Checking file: " ++ fileStr path
+    exists <- liftIO $ isFile path
+    if exists
+        then Just <$>
+             (FileEntry
+                  <$> liftIO (getModified path)
+                  <*> pure ent
+                  <*> if getHash
+                      then do contents <- liftIO $ B.readFile (fileStr path)
+                              hashContents (BlobString contents)
+                      else return (blobEntryOid ent))
+        else return Nothing
+
+fileStr :: FilePath -> String
+fileStr = TL.unpack . toTextIgnore
+
+infoL :: (Repository m, MonadIO m) => String -> m ()
+infoL = liftIO . infoM "git-monitor"
+
+debugL :: (Repository m, MonadIO m) => String -> m ()
+debugL = liftIO . debugM "git-monitor"
+
+-- Main.hs (git-monitor) ends here
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
diff --git a/git-monitor.cabal b/git-monitor.cabal
new file mode 100644
--- /dev/null
+++ b/git-monitor.cabal
@@ -0,0 +1,41 @@
+Name:    git-monitor
+Version: 1.0.0
+
+Synopsis:    Passively snapshots working tree changes efficiently.
+Description: Passively snapshots working tree changes efficiently.
+
+Homepage:           git://github.com/fpco/gitlib
+License:            BSD3
+License-file:       LICENSE
+Author:             John Wiegley
+Maintainer:         John Wiegley <johnw@fpcomplete.com>
+Category:           Development
+Build-type:         Simple
+Cabal-version:      >= 1.10
+
+Extra-Source-Files: README.md
+
+Executable git-monitor
+    Main-is: Main.hs
+    Default-language: Haskell2010
+    ghc-options: -Wall
+
+    Build-depends: base                 >= 4 && < 5
+                 , bytestring           >= 0.9.2.1
+                 , containers           >= 0.4.2.1
+                 , gitlib               >= 1.0.1
+                 , gitlib-utils         >= 1.0.1
+                 , gitlib-libgit2       >= 1.0.1
+                 , hslogger             >= 1.2
+                 , old-locale           >= 1.0.0.4
+                 , optparse-applicative >= 0.5.2.1
+                 , shelly               >= 0.14
+                 , system-fileio        >= 0.3.11
+                 , system-filepath      >= 0.4.7
+                 , text                 >= 0.11.2
+                 , time                 >= 1.4
+                 , transformers         >= 0.3.0.0
+
+Source-repository head
+  type:     git
+  location: git://github.com/fpco/gitlib
