diff --git a/Git/Blob.hs b/Git/Blob.hs
--- a/Git/Blob.hs
+++ b/Git/Blob.hs
@@ -2,10 +2,14 @@
 
 import           Control.Applicative
 import           Control.Monad
+import           Control.Monad.IO.Class
 import           Control.Monad.Trans.Class
+import           Control.Monad.Trans.Resource
 import           Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
 import           Data.Conduit
-import qualified Data.Conduit.List as CList
+import qualified Data.Conduit.List as CL
+import qualified Data.Conduit.Binary as CB
 import           Data.HashSet (HashSet)
 import qualified Data.HashSet as HashSet
 import           Data.Tagged
@@ -13,38 +17,62 @@
 import           Data.Text.Encoding as T
 import           Git.Types
 
-createBlobUtf8 :: Repository m => Text -> m (BlobOid m)
+createBlobUtf8 :: MonadGit r m => Text -> m (BlobOid r)
 createBlobUtf8 = createBlob . BlobString . T.encodeUtf8
 
-catBlob :: Repository m => BlobOid m -> m ByteString
+catBlob :: MonadGit r m => BlobOid r -> m ByteString
 catBlob = lookupBlob >=> blobToByteString
 
-catBlobUtf8 :: Repository m => BlobOid m -> m Text
+catBlobLazy :: MonadGit r m => BlobOid r -> m BL.ByteString
+catBlobLazy = lookupBlob >=> blobToLazyByteString
+
+catBlobUtf8 :: MonadGit r m => BlobOid r -> m Text
 catBlobUtf8 = catBlob >=> return . T.decodeUtf8
 
-blobContentsToByteString :: Repository m => BlobContents m -> m ByteString
+blobContentsToByteString :: MonadGit r m => BlobContents m -> m ByteString
 blobContentsToByteString (BlobString bs) = return bs
+blobContentsToByteString (BlobStringLazy bs) =
+    return $ B.concat (BL.toChunks bs)
 blobContentsToByteString (BlobStream bs) =
-    B.concat <$> (bs $$ CList.consume)
+    B.concat <$> (bs $$ CL.consume)
 blobContentsToByteString (BlobSizedStream bs _) =
-    B.concat <$> (bs $$ CList.consume)
+    B.concat <$> (bs $$ CL.consume)
 
-blobToByteString :: Repository m => Blob m -> m ByteString
+blobToByteString :: MonadGit r m => Blob r m -> m ByteString
 blobToByteString (Blob _ contents) = blobContentsToByteString contents
 
-treeBlobEntries :: Repository m
-                => Tree m -> m [(TreeFilePath, BlobOid m, BlobKind)]
-treeBlobEntries tree = do
-    entries <- listTreeEntries tree
-    return $ Prelude.foldr f [] entries
+blobContentsToLazyByteString :: MonadGit r m
+                             => BlobContents m -> m BL.ByteString
+blobContentsToLazyByteString (BlobString bs) = return $ BL.fromChunks [bs]
+blobContentsToLazyByteString (BlobStringLazy bs) = return bs
+blobContentsToLazyByteString (BlobStream bs) = bs $$ CB.sinkLbs
+blobContentsToLazyByteString (BlobSizedStream bs _) = bs $$ CB.sinkLbs
+
+blobToLazyByteString :: MonadGit r m => Blob r m -> m BL.ByteString
+blobToLazyByteString (Blob _ contents) = blobContentsToLazyByteString contents
+
+writeBlob :: (MonadGit r m, MonadIO m, MonadResource m)
+          => FilePath -> BlobContents m -> m ()
+writeBlob path (BlobString bs)         = liftIO $ B.writeFile path bs
+writeBlob path (BlobStringLazy bs)     = CB.sourceLbs bs $$ CB.sinkFile path
+writeBlob path (BlobStream str)        = str $$ CB.sinkFile path
+writeBlob path (BlobSizedStream str _) = str $$ CB.sinkFile path
+
+treeBlobEntries :: MonadGit r m
+                => Tree r -> m [(TreeFilePath, BlobOid r, BlobKind)]
+treeBlobEntries tree = sourceTreeBlobEntries tree $$ CL.consume
+
+sourceTreeBlobEntries
+    :: MonadGit r m
+    => Tree r -> Producer m (TreeFilePath, BlobOid r, BlobKind)
+sourceTreeBlobEntries tree =
+    sourceTreeEntries tree =$= awaitForever go
   where
-    f (fp ,BlobEntry oid k) xs = (fp, oid, k):xs
-    f _ xs = xs
+    go (fp ,BlobEntry oid k) = yield (fp, oid, k)
+    go _ = return ()
 
-copyBlob :: (Repository m, Repository (t m), MonadTrans t)
-         => BlobOid m
-         -> HashSet Text
-         -> t m (BlobOid (t m), HashSet Text)
+copyBlob :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
+         => BlobOid r -> HashSet Text -> t m (BlobOid s, HashSet Text)
 copyBlob blobr needed = do
     let oid = untag blobr
         sha = renderOid oid
diff --git a/Git/Commit.hs b/Git/Commit.hs
--- a/Git/Commit.hs
+++ b/Git/Commit.hs
@@ -17,20 +17,19 @@
 import           Git.Types
 import           Prelude hiding (FilePath)
 
-commitTreeEntry :: Repository m
-                => Commit m
-                -> TreeFilePath
-                -> m (Maybe (TreeEntry m))
+commitTreeEntry :: MonadGit r m
+                => Commit r -> TreeFilePath -> m (Maybe (TreeEntry r))
 commitTreeEntry c path = flip treeEntry path =<< lookupTree (commitTree c)
 
-copyCommitOid :: (Repository m, Repository n) => CommitOid m -> n (CommitOid n)
+copyCommitOid :: (IsOid (Oid r), MonadGit s n)
+              => CommitOid r -> n (CommitOid s)
 copyCommitOid = parseObjOid . renderObjOid
 
-copyCommit :: (Repository m, Repository (t m), MonadTrans t)
-           => CommitOid m
+copyCommit :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
+           => CommitOid r
            -> Maybe RefName
            -> HashSet Text
-           -> t m (CommitOid (t m), HashSet Text)
+           -> t m (CommitOid s, HashSet Text)
 copyCommit cr mref needed = do
     let oid = untag cr
         sha = renderOid oid
@@ -66,17 +65,17 @@
         let x = cref2 `seq` (cref2:prefs)
         return $ x `seq` needed'' `seq` (x,needed'')
 
-listCommits :: Repository m
-            => Maybe (CommitOid m) -- ^ A commit we may already have
-            -> CommitOid m         -- ^ The commit we need
-            -> m [CommitOid m]     -- ^ All the objects in between
+listCommits :: MonadGit r m
+            => Maybe (CommitOid r) -- ^ A commit we may already have
+            -> CommitOid r         -- ^ The commit we need
+            -> m [CommitOid r]     -- ^ All the objects in between
 listCommits mhave need =
     sourceObjects mhave need False
         $= CL.mapM (\(CommitObjOid c) -> return c)
         $$ CL.consume
 
-traverseCommits :: Repository m => (CommitOid m -> m a) -> CommitOid m -> m [a]
+traverseCommits :: MonadGit r m => (CommitOid r -> m a) -> CommitOid r -> m [a]
 traverseCommits f need = mapM f =<< listCommits Nothing need
 
-traverseCommits_ :: Repository m => (CommitOid m -> m ()) -> CommitOid m -> m ()
+traverseCommits_ :: MonadGit r m => (CommitOid r -> m ()) -> CommitOid r -> m ()
 traverseCommits_ = (void .) . traverseCommits
diff --git a/Git/Commit/Push.hs b/Git/Commit/Push.hs
--- a/Git/Commit/Push.hs
+++ b/Git/Commit/Push.hs
@@ -1,8 +1,11 @@
 module Git.Commit.Push where
 
+import           Control.Applicative
 import           Control.Failure
 import           Control.Monad
+import           Control.Monad.IO.Class
 import           Control.Monad.Trans.Class
+import           Control.Monad.Trans.Control
 import           Data.Function
 import qualified Data.HashSet as HashSet
 import           Data.List
@@ -14,15 +17,16 @@
 import           Git.Commit
 import           Git.Object
 import           Git.Reference
+import           Git.Repository
 import           Git.Types
-import           Prelude hiding (FilePath)
+import           Prelude
 
 -- | Fast-forward push a reference between repositories using a recursive
 --   copy.  This can be extremely slow, but always works no matter which two
 --   backends are being used.  It should be considered a matter of last
 --   resort, or for objects sets that are known to be small.
-pushCommit :: (Repository m, Repository (t m), MonadTrans t)
-           => CommitOid m -> Text -> t m (CommitOid (t m))
+pushCommit :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
+           => CommitOid r -> Text -> t m (CommitOid s)
 pushCommit coid remoteRefName = do
     commits <- mapM copyCommitOid =<< lift (listCommits Nothing coid)
     mrref   <- fmap Tagged `liftM` resolveReference remoteRefName
@@ -41,3 +45,39 @@
     -- jww (2013-04-18): This is something the user must decide to do
     -- updateReference_ remoteRefName (RefObj cref)
     return cref
+
+copyRepository :: (MonadGit r m, MonadIO m, MonadBaseControl IO m,
+                   MonadGit s (t m), MonadTrans t, MonadBaseControl IO (t m))
+                => RepositoryFactory (t m) m s
+                -> Maybe (CommitOid r)
+                -> Text
+                -> FilePath
+                -> Bool
+                -> m ()
+copyRepository factory mname refName gitDir isBare =
+    withRepository' factory RepositoryOptions
+        { repoPath       = gitDir
+        , repoIsBare     = isBare
+        , repoAutoCreate = True
+        }
+        (maybe (return ()) go mname)
+  where
+    go coid = do
+        -- jww (2013-04-24): We don't need do download every object back to
+        -- the first commit, but only the commits (and their objects) back to
+        -- and including the common ancestor.  The question is, how do we
+        -- determine the common ancestor before we've fetched all the contents
+        -- of at least one side?
+        cref <- pushCommit coid refName
+
+        -- This will always be a fast-forward, since temp.git is empty.  The
+        -- resulting HEAD will have the refname as the ref we want to push to
+        -- or pull from, and no others.
+        updateReference refName (RefObj (untag cref))
+        updateReference "HEAD" (RefSymbolic refName)
+
+        mref <- fmap renderOid <$> resolveReference refName
+        unless (maybe False (renderObjOid coid ==) mref) $
+            failure (BackendError $
+                     "Could not resolve destination reference '"
+                     <> refName <> "'in project")
diff --git a/Git/Object.hs b/Git/Object.hs
--- a/Git/Object.hs
+++ b/Git/Object.hs
@@ -9,42 +9,36 @@
 import           Git.Types
 import           Prelude hiding (FilePath)
 
-listObjects :: Repository m
-            => Maybe (CommitOid m) -- ^ A commit we may already have
-            -> CommitOid m         -- ^ The commit we need
+listObjects :: MonadGit r m
+            => Maybe (CommitOid r) -- ^ A commit we may already have
+            -> CommitOid r         -- ^ The commit we need
             -> Bool                -- ^ Include commit trees also?
-            -> m [ObjectOid m]     -- ^ All the objects in between
+            -> m [ObjectOid r]     -- ^ All the objects in between
 listObjects mhave need alsoTrees =
     sourceObjects mhave need alsoTrees $$ CL.consume
 
-traverseObjects :: Repository m => (ObjectOid m -> m a) -> CommitOid m -> m [a]
+traverseObjects :: MonadGit r m => (ObjectOid r -> m a) -> CommitOid r -> m [a]
 traverseObjects f need = mapM f =<< listObjects Nothing need False
 
-traverseObjects_ :: Repository m => (ObjectOid m -> m ()) -> CommitOid m -> m ()
+traverseObjects_ :: MonadGit r m => (ObjectOid r -> m ()) -> CommitOid r -> m ()
 traverseObjects_ = (void .) . traverseObjects
 
 -- | Given a list of objects (commit and top-level trees) return by
 --   'listObjects', expand it to include all subtrees and blobs as well.
 --   Ordering is preserved.
-expandTreeObjects :: Repository m => Conduit (ObjectOid m) m (ObjectOid m)
-expandTreeObjects = whileJust_ await $ \obj -> case obj of
+expandTreeObjects :: MonadGit r m => Conduit (ObjectOid r) m (ObjectOid r)
+expandTreeObjects = awaitForever $ \obj -> case obj of
     TreeObjOid toid -> do
         yield $ TreeObjOid toid
         tr <- lift $ lookupTree toid
-        ents <- lift $ listTreeEntries tr
-        forM_ ents $ \ent -> case ent of
-            (_, BlobEntry oid _) -> yield $ BlobObjOid oid
-            (_, TreeEntry oid)   -> yield $ TreeObjOid oid
-            _ -> return ()
+        sourceTreeEntries tr
+            =$= awaitForever (\ent -> case ent of
+                (_, BlobEntry oid _) -> yield $ BlobObjOid oid
+                (_, TreeEntry oid)   -> yield $ TreeObjOid oid
+                _ -> return ())
     _ -> yield obj
-  where
-    whileJust_ p f = do
-        x <- p
-        case x of
-            Nothing -> return ()
-            Just x' -> f x' >> whileJust_ p f
 
-listAllObjects :: Repository m
-               => Maybe (CommitOid m) -> CommitOid m -> m [ObjectOid m]
+listAllObjects :: MonadGit r m
+               => Maybe (CommitOid r) -> CommitOid r -> m [ObjectOid r]
 listAllObjects mhave need =
     sourceObjects mhave need True $= expandTreeObjects $$ CL.consume
diff --git a/Git/Reference.hs b/Git/Reference.hs
--- a/Git/Reference.hs
+++ b/Git/Reference.hs
@@ -1,12 +1,17 @@
 module Git.Reference where
 
 import Git.Types
+import Data.Conduit
+import Data.Conduit.List as CL
 
-resolveReference :: Repository m => RefName -> m (Maybe (Oid m))
+listReferences :: MonadGit r m => m [RefName]
+listReferences = sourceReferences $$ CL.consume
+
+resolveReference :: MonadGit r m => RefName -> m (Maybe (Oid r))
 resolveReference name = do
     mref <- lookupReference name
     maybe (return Nothing) referenceToOid mref
 
-referenceToOid :: Repository m => RefTarget m -> m (Maybe (Oid m))
+referenceToOid :: MonadGit r m => RefTarget r -> m (Maybe (Oid r))
 referenceToOid (RefObj oid)       = return $ Just oid
 referenceToOid (RefSymbolic name) = resolveReference name
diff --git a/Git/Repository.hs b/Git/Repository.hs
--- a/Git/Repository.hs
+++ b/Git/Repository.hs
@@ -1,24 +1,22 @@
 module Git.Repository where
 
-import qualified Control.Exception.Lifted as Exc
-import           Control.Monad
-import           Control.Monad.IO.Class
-import           Data.Conduit
-import           Git.Types
-import           System.Directory
-import           System.Mem
+import Control.Exception.Lifted
+import Control.Monad
+import Control.Monad.IO.Class
+import Data.Conduit
+import Git.Types
+import System.Directory
 
-withNewRepository :: (Repository t, MonadGit t,
-                      MonadBaseControl IO m, MonadIO m)
-                  => RepositoryFactory t m c
-                  -> FilePath -> t a -> m a
+withNewRepository :: (MonadGit r n, MonadBaseControl IO n, MonadIO m)
+                  => RepositoryFactory n m r
+                  -> FilePath -> n a -> m a
 withNewRepository factory path action = do
     liftIO $ do
         exists <- doesDirectoryExist path
         when exists $ removeDirectoryRecursive path
 
     -- we want exceptions to leave the repo behind
-    a <- withRepository' factory (defaultOptions factory)
+    a <- withRepository' factory RepositoryOptions
         { repoPath       = path
         , repoIsBare     = True
         , repoAutoCreate = True
@@ -30,12 +28,12 @@
 
     return a
 
-withNewRepository' :: (Repository t, MonadGit t,
+withNewRepository' :: (MonadGit r n, MonadBaseControl IO n,
                        MonadBaseControl IO m, MonadIO m)
-                   => RepositoryFactory t m c -> FilePath -> t a -> m a
+                   => RepositoryFactory n m r -> FilePath -> n a -> m a
 withNewRepository' factory path action =
-    Exc.bracket_ recover recover $
-        withRepository' factory (defaultOptions factory)
+    bracket_ recover recover $
+        withRepository' factory RepositoryOptions
             { repoPath       = path
             , repoIsBare     = True
             , repoAutoCreate = True
@@ -45,30 +43,13 @@
         exists <- doesDirectoryExist path
         when exists $ removeDirectoryRecursive path
 
-withBackendDo :: (MonadIO m, MonadBaseControl IO m)
-              => RepositoryFactory t m a -> m b -> m b
-withBackendDo fact f = do
-    startupBackend fact
-    Exc.finally f (liftIO performGC >> shutdownBackend fact)
-
-withRepository' :: (Repository t,
-                    MonadBaseControl IO m, MonadIO m)
-                => RepositoryFactory t m c
-                -> RepositoryOptions
-                -> t a
-                -> m a
-withRepository' factory opts action =
-    Exc.bracket
-        (openRepository factory opts)
-        (closeRepository factory)
-        (flip (runRepository factory) action)
+withRepository' :: (MonadGit r n, MonadBaseControl IO n, MonadIO m)
+                => RepositoryFactory n m r -> RepositoryOptions -> n a -> m a
+withRepository' factory opts action = do
+    repo <- openRepository factory opts
+    runRepository factory repo $ action `finally` closeRepository
 
-withRepository :: (Repository t,
-                   MonadBaseControl IO m, MonadIO m)
-               => RepositoryFactory t m c
-               -> FilePath
-               -> t a
-               -> m a
+withRepository :: (MonadGit r n, MonadBaseControl IO n, MonadIO m)
+               => RepositoryFactory n m r -> FilePath -> n a -> m a
 withRepository factory path =
-    withRepository' factory
-        (defaultOptions factory) { repoPath = path }
+    withRepository' factory defaultRepositoryOptions { repoPath = path }
diff --git a/Git/Tree.hs b/Git/Tree.hs
--- a/Git/Tree.hs
+++ b/Git/Tree.hs
@@ -3,6 +3,8 @@
 import           Control.Failure
 import           Control.Monad
 import           Control.Monad.Trans.Class
+import           Data.Conduit
+import qualified Data.Conduit.List as CL
 import           Data.HashSet (HashSet)
 import qualified Data.HashSet as HashSet
 import           Data.Monoid
@@ -12,10 +14,11 @@
 import           Git.Tree.Builder
 import           Git.Types
 
-copyTreeEntry :: (Repository m, Repository (t m), MonadTrans t)
-              => TreeEntry m
-              -> HashSet Text
-              -> t m (TreeEntry (t m), HashSet Text)
+listTreeEntries :: MonadGit r m => Tree r -> m [(TreeFilePath, TreeEntry r)]
+listTreeEntries tree = sourceTreeEntries tree $$ CL.consume
+
+copyTreeEntry :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
+              => TreeEntry r -> HashSet Text -> t m (TreeEntry s, HashSet Text)
 copyTreeEntry (BlobEntry oid kind) needed = do
     (b,needed') <- copyBlob oid needed
     unless (renderObjOid oid == renderObjOid b) $
@@ -27,10 +30,8 @@
     return (CommitEntry (Tagged coid), needed)
 copyTreeEntry (TreeEntry _) _ = error "This should never be called"
 
-copyTree :: (Repository m, Repository (t m), MonadTrans t)
-         => TreeOid m
-         -> HashSet Text
-         -> t m (TreeOid (t m), HashSet Text)
+copyTree :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
+         => TreeOid r -> HashSet Text -> t m (TreeOid s, HashSet Text)
 copyTree tr needed = do
     let oid = untag tr
         sha = renderOid oid
@@ -47,10 +48,10 @@
 
         else return (Tagged oid2, needed)
   where
-    doCopyTreeEntry :: (Repository m, Repository (t m), MonadTrans t)
+    doCopyTreeEntry :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
                     => HashSet Text
-                    -> (TreeFilePath, TreeEntry m)
-                    -> TreeT (t m) (HashSet Text)
+                    -> (TreeFilePath, TreeEntry r)
+                    -> TreeT s (t m) (HashSet Text)
     doCopyTreeEntry set (_, TreeEntry {}) = return set
     doCopyTreeEntry set (fp, ent) = do
         (ent2,set') <- lift $ copyTreeEntry ent set
diff --git a/Git/Tree/Builder.hs b/Git/Tree/Builder.hs
--- a/Git/Tree/Builder.hs
+++ b/Git/Tree/Builder.hs
@@ -39,57 +39,57 @@
 import           Data.Word
 import           Git.Types
 
-data ModifyTreeResult m = TreeEntryNotFound
+data ModifyTreeResult r = TreeEntryNotFound
                         | TreeEntryDeleted
-                        | TreeEntryPersistent (TreeEntry m)
-                        | TreeEntryMutated (TreeEntry m)
+                        | TreeEntryPersistent (TreeEntry r)
+                        | TreeEntryMutated (TreeEntry r)
 
-fromModifyTreeResult :: ModifyTreeResult m -> Maybe (TreeEntry m)
+fromModifyTreeResult :: ModifyTreeResult r -> Maybe (TreeEntry r)
 fromModifyTreeResult TreeEntryNotFound       = Nothing
 fromModifyTreeResult TreeEntryDeleted        = Nothing
 fromModifyTreeResult (TreeEntryPersistent x) = Just x
 fromModifyTreeResult (TreeEntryMutated x)    = Just x
 
-toModifyTreeResult :: (TreeEntry m -> ModifyTreeResult m)
-                   -> Maybe (TreeEntry m)
-                   -> ModifyTreeResult m
+toModifyTreeResult :: (TreeEntry r -> ModifyTreeResult r)
+                   -> Maybe (TreeEntry r)
+                   -> ModifyTreeResult r
 toModifyTreeResult _ Nothing  = TreeEntryNotFound
 toModifyTreeResult f (Just x) = f x
 
-newtype TreeT m a = TreeT { runTreeT :: StateT (TreeBuilder m) m a }
+newtype TreeT r m a = TreeT { runTreeT :: StateT (TreeBuilder r m) m a }
 
-instance Functor m => Functor (TreeT m) where
+instance Functor m => Functor (TreeT r m) where
     fmap f (TreeT t) = TreeT (fmap f t)
 
-instance Monad m => Monad (TreeT m) where
+instance Monad m => Monad (TreeT r m) where
     return x = TreeT (return x)
     TreeT x >>= f = TreeT (x >>= runTreeT . f)
 
-instance (Functor m, Monad m) => Applicative (TreeT m) where
+instance (Functor m, Monad m) => Applicative (TreeT r m) where
     pure = return
     (<*>) = ap
 
-instance (Functor m, MonadPlus m) => Alternative (TreeT m) where
+instance (Functor m, MonadPlus m) => Alternative (TreeT r m) where
     empty = mzero
     (<|>) = mplus
 
-instance (MonadPlus m) => MonadPlus (TreeT m) where
+instance (MonadPlus m) => MonadPlus (TreeT r m) where
     mzero       = TreeT mzero
     m `mplus` n = TreeT $ runTreeT m `mplus` runTreeT n
 
-instance (MonadFix m) => MonadFix (TreeT m) where
+instance (MonadFix m) => MonadFix (TreeT r m) where
     mfix f = TreeT $ mfix $ \ ~a -> runTreeT (f a)
 
-instance MonadTrans TreeT where
+instance MonadTrans (TreeT r) where
     lift m = TreeT $ lift m
 
-instance (MonadIO m) => MonadIO (TreeT m) where
+instance (MonadIO m) => MonadIO (TreeT r m) where
     liftIO = lift . liftIO
 
-getBuilder :: Monad m => TreeT m (TreeBuilder m)
+getBuilder :: Monad m => TreeT r m (TreeBuilder r m)
 getBuilder = TreeT get
 
-putBuilder :: Monad m => TreeBuilder m -> TreeT m ()
+putBuilder :: Monad m => TreeBuilder r m -> TreeT r m ()
 putBuilder = TreeT . put
 
 data BuilderAction = GetEntry | PutEntry | DropEntry
@@ -103,12 +103,12 @@
 --
 --   This is a complex algorithm which has been rewritten many times, so I
 --   will try to guide you through it as best I can.
-queryTreeBuilder :: Repository m
-                 => TreeBuilder m
+queryTreeBuilder :: MonadGit r m
+                 => TreeBuilder r m
                  -> TreeFilePath
                  -> BuilderAction
-                 -> (Maybe (TreeEntry m) -> ModifyTreeResult m)
-                 -> m (TreeBuilder m, Maybe (TreeEntry m))
+                 -> (Maybe (TreeEntry r) -> ModifyTreeResult r)
+                 -> m (TreeBuilder r m, Maybe (TreeEntry r))
 queryTreeBuilder builder path kind f = do
     (mtb, mtresult) <- walk (BuilderUnchanged builder) (splitDirectories path)
     return (fromBuilderMod mtb, fromModifyTreeResult mtresult)
@@ -200,8 +200,8 @@
 
 -- | Write out a tree to its repository.  If it has already been written,
 --   nothing will happen.
-writeTreeBuilder :: Repository m
-                 => TreeBuilder m -> m (TreeBuilder m, TreeOid m)
+writeTreeBuilder :: MonadGit r m
+                 => TreeBuilder r m -> m (TreeBuilder r m, TreeOid r)
 writeTreeBuilder builder = do
     (bm, mtref) <- go (BuilderUnchanged builder)
     tref <- case mtref of
@@ -235,70 +235,71 @@
             Just tref -> mtbPutEntry tb tb k (TreeEntry tref)
         return $ bm <> bm'
 
-getEntry :: Repository m => TreeFilePath -> TreeT m (Maybe (TreeEntry m))
+getEntry :: MonadGit r m => TreeFilePath -> TreeT r m (Maybe (TreeEntry r))
 getEntry path = do
     tb <- getBuilder
     snd <$> lift (queryTreeBuilder tb path GetEntry
                   (toModifyTreeResult TreeEntryPersistent))
 
-putEntry :: Repository m => TreeFilePath -> TreeEntry m -> TreeT m ()
+putEntry :: MonadGit r m => TreeFilePath -> TreeEntry r -> TreeT r m ()
 putEntry path ent = do
     tb  <- getBuilder
     tb' <- fst <$> lift (queryTreeBuilder tb path PutEntry
                          (const (TreeEntryMutated ent)))
     putBuilder tb'
 
-dropEntry :: Repository m => TreeFilePath -> TreeT m ()
+dropEntry :: MonadGit r m => TreeFilePath -> TreeT r m ()
 dropEntry path = do
     tb  <- getBuilder
     tb' <- fst <$> lift (queryTreeBuilder tb path DropEntry
                          (const TreeEntryDeleted))
     putBuilder tb'
 
-putBlob' :: Repository m => TreeFilePath -> BlobOid m -> BlobKind -> TreeT m ()
+putBlob' :: MonadGit r m
+         => TreeFilePath -> BlobOid r -> BlobKind -> TreeT r m ()
 putBlob' path b kind = putEntry path (BlobEntry b kind)
 
-putBlob :: Repository m => TreeFilePath -> BlobOid m -> TreeT m ()
+putBlob :: MonadGit r m => TreeFilePath -> BlobOid r -> TreeT r m ()
 putBlob path b = putBlob' path b PlainBlob
 
-putTree :: Repository m => TreeFilePath -> TreeOid m -> TreeT m ()
+putTree :: MonadGit r m => TreeFilePath -> TreeOid r -> TreeT r m ()
 putTree path t = putEntry path (TreeEntry t)
 
-putCommit :: Repository m => TreeFilePath -> CommitOid m -> TreeT m ()
+putCommit :: MonadGit r m => TreeFilePath -> CommitOid r -> TreeT r m ()
 putCommit path c = putEntry path (CommitEntry c)
 
-doWithTree :: Repository m => Maybe (Tree m) -> TreeT m a -> m (a, TreeOid m)
-doWithTree mtr act =
-    fst <$> (runStateT (runTreeT go) =<< newTreeBuilder mtr)
+doWithTree :: MonadGit r m => Maybe (Tree r) -> TreeT r m a -> m (a, TreeOid r)
+doWithTree rtr act =
+    fst <$> (runStateT (runTreeT go) =<< newTreeBuilder rtr)
   where
     go = liftM2 (,) act currentTreeOid
 
-withTree :: Repository m => Tree m -> TreeT m a -> m (a, TreeOid m)
+withTree :: MonadGit r m => Tree r -> TreeT r m a -> m (a, TreeOid r)
 withTree tr = doWithTree (Just tr)
 
-withTreeOid :: Repository m => TreeOid m -> TreeT m a -> m (a, TreeOid m)
+withTreeOid :: MonadGit r m => TreeOid r -> TreeT r m a -> m (a, TreeOid r)
 withTreeOid oid action = do
     tree <- lookupTree oid
     doWithTree (Just tree) action
 
-mutateTree :: Repository m => Tree m -> TreeT m a -> m (TreeOid m)
+mutateTree :: MonadGit r m => Tree r -> TreeT r m a -> m (TreeOid r)
 mutateTree tr action = snd <$> withTree tr action
 
-mutateTreeOid :: Repository m => TreeOid m -> TreeT m a -> m (TreeOid m)
+mutateTreeOid :: MonadGit r m => TreeOid r -> TreeT r m a -> m (TreeOid r)
 mutateTreeOid tr action = snd <$> withTreeOid tr action
 
-currentTreeOid :: Repository m => TreeT m (TreeOid m)
+currentTreeOid :: MonadGit r m => TreeT r m (TreeOid r)
 currentTreeOid = do
     tb <- getBuilder
     (tb', toid) <- lift $ writeTreeBuilder tb
     putBuilder tb'
     return toid
 
-currentTree :: Repository m => TreeT m (Tree m)
+currentTree :: MonadGit r m => TreeT r m (Tree r)
 currentTree = lift . lookupTree =<< currentTreeOid
 
-withNewTree :: Repository m => TreeT m a -> m (a, TreeOid m)
+withNewTree :: MonadGit r m => TreeT r m a -> m (a, TreeOid r)
 withNewTree = doWithTree Nothing
 
-createTree :: Repository m => TreeT m a -> m (TreeOid m)
+createTree :: MonadGit r m => TreeT r m a -> m (TreeOid r)
 createTree action = snd <$> withNewTree action
diff --git a/Git/Tree/Builder/Pure.hs b/Git/Tree/Builder/Pure.hs
--- a/Git/Tree/Builder/Pure.hs
+++ b/Git/Tree/Builder/Pure.hs
@@ -4,40 +4,43 @@
        ) where
 
 import           Control.Applicative
-import           Data.Monoid
 import           Data.HashMap.Strict (HashMap)
 import qualified Data.HashMap.Strict as HashMap
+import           Data.Monoid
+import           Data.Traversable
 import           Git
+import           Prelude hiding (mapM)
 
-type EntryHashMap m = HashMap TreeFilePath (TreeEntry m)
+type EntryHashMap r = HashMap TreeFilePath (TreeEntry r)
 
 -- | Create a new, empty tree.
 --
 --   Since empty trees cannot exist in Git, attempting to write out an empty
 --   tree is a no-op.
-newPureTreeBuilder :: Repository m
-                   => (Tree m -> m (EntryHashMap m))
-                   -> (EntryHashMap m -> m (TreeOid m))
-                   -> Maybe (Tree m)
-                   -> m (TreeBuilder m)
+newPureTreeBuilder :: MonadGit r m
+                   => (Tree r -> m (EntryHashMap r))
+                   -> (EntryHashMap r -> m (TreeOid r))
+                   -> Maybe (Tree r)
+                   -> m (TreeBuilder r m)
 newPureTreeBuilder reader writer mtree = do
     entMap <- case mtree of
         Nothing   -> return HashMap.empty
         Just tree -> reader tree
+    toid <- mapM treeOid mtree
     return $ makePureBuilder
-        (treeOid <$> mtree)
+        toid
         mempty
         (newPureTreeBuilder reader writer)
         entMap
         writer
 
-makePureBuilder :: Repository m
-                => Maybe (TreeOid m)
-                -> HashMap TreeFilePath (TreeBuilder m)
-                -> (Maybe (Tree m) -> m (TreeBuilder m))
-                -> EntryHashMap m
-                -> (EntryHashMap m -> m (TreeOid m))
-                -> TreeBuilder m
+makePureBuilder :: MonadGit r m
+                => Maybe (TreeOid r)
+                -> HashMap TreeFilePath (TreeBuilder r m)
+                -> (Maybe (Tree r) -> m (TreeBuilder r m))
+                -> EntryHashMap r
+                -> (EntryHashMap r -> m (TreeOid r))
+                -> TreeBuilder r m
 makePureBuilder baseTree upds newBuilder entMap writer = TreeBuilder
     { mtbBaseTreeOid    = baseTree
     , mtbPendingUpdates = upds
diff --git a/Git/Types.hs b/Git/Types.hs
--- a/Git/Types.hs
+++ b/Git/Types.hs
@@ -3,23 +3,23 @@
 import           Control.Applicative
 import qualified Control.Exception.Lifted as Exc
 import           Control.Failure
-import           Control.Monad.IO.Class
-import qualified Data.Binary as Bin
+import           Control.Monad
+import           Control.Monad.Trans.Class
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString.Base16 as B16
+import qualified Data.ByteString.Lazy as BL
 import           Data.Conduit
-import           Data.Default
+import qualified Data.Conduit.List as CL
 import           Data.HashMap.Strict (HashMap)
 import           Data.Hashable
 import           Data.Map (Map)
-import           Data.Monoid
+import           Data.Semigroup
 import           Data.Tagged
 import           Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
 import           Data.Time
 import           Data.Typeable
---import           System.Posix.ByteString.FilePath
 
 type RawFilePath = ByteString
 
@@ -27,66 +27,68 @@
     { hasSymbolicReferences :: !Bool
     } deriving Show
 
-type MonadGit m = (Failure GitException m, Applicative m,
-                   MonadIO m, MonadBaseControl IO m)
-
-type RefName = Text
-type CommitAuthor = Text
-type CommitEmail = Text
+type RefName       = Text
+type CommitAuthor  = Text
+type CommitEmail   = Text
 type CommitMessage = Text
-type TreeFilePath = RawFilePath
+type TreeFilePath  = RawFilePath
 
 -- | 'Repository' is the central point of contact between user code and Git
 --   data objects.  Every object must belong to some repository.
-class (Applicative m, Monad m, Failure GitException m, IsOid (Oid m))
-      => Repository m where
-    type Oid m :: *
-    data Tree m :: *
-    data Options m :: *
+class (Applicative m, Monad m, Failure GitException m,
+       IsOid (Oid r), Show (Oid r), Eq (Oid r), Ord (Oid r))
+      => MonadGit r m | m -> r where
+    type Oid r :: *
+    data Tree r :: *
+    data Options r :: *
 
     facts :: m RepositoryFacts
-    parseOid :: Text -> m (Oid m)
+    parseOid :: Text -> m (Oid r)
+
+    getRepository :: m r
+    closeRepository :: m ()
     deleteRepository :: m ()
 
     -- References
-    createReference :: RefName -> RefTarget m -> m ()
-    lookupReference :: RefName -> m (Maybe (RefTarget m))
-    updateReference :: RefName -> RefTarget m -> m ()
+    createReference :: RefName -> RefTarget r -> m ()
+    lookupReference :: RefName -> m (Maybe (RefTarget r))
+    updateReference :: RefName -> RefTarget r -> m ()
     deleteReference :: RefName -> m ()
-    listReferences  :: m [RefName]
+    sourceReferences :: Producer m RefName
 
     -- Object lookup
-    lookupCommit  :: CommitOid m -> m (Commit m)
-    lookupTree    :: TreeOid m -> m (Tree m)
-    lookupBlob    :: BlobOid m -> m (Blob m)
-    lookupTag     :: TagOid m -> m (Tag m)
-    lookupObject  :: Oid m -> m (Object m)
-    existsObject  :: Oid m -> m Bool
-    sourceObjects :: Maybe (CommitOid m)    -- ^ A commit we may already have
-                  -> CommitOid m            -- ^ The commit we need
+    lookupObject  :: Oid r -> m (Object r m)
+    existsObject  :: Oid r -> m Bool
+    sourceObjects :: Maybe (CommitOid r)    -- ^ A commit we may already have
+                  -> CommitOid r            -- ^ The commit we need
                   -> Bool                   -- ^ Include commit trees also?
-                  -> Producer m (ObjectOid m) -- ^ All the objects in between
+                  -> Producer m (ObjectOid r) -- ^ All the objects in between
 
+    lookupCommit  :: CommitOid r -> m (Commit r)
+    lookupTree    :: TreeOid r -> m (Tree r)
+    lookupBlob    :: BlobOid r -> m (Blob r m)
+    lookupTag     :: TagOid r -> m (Tag r)
+
     -- Working with trees
-    newTreeBuilder :: Maybe (Tree m) -> m (TreeBuilder m)
+    newTreeBuilder :: Maybe (Tree r) -> m (TreeBuilder r m)
 
-    treeOid   :: Tree m -> TreeOid m
-    treeEntry :: Tree m -> TreeFilePath -> m (Maybe (TreeEntry m))
-    listTreeEntries :: Tree m -> m [(TreeFilePath, TreeEntry m)]
+    treeOid   :: Tree r -> m (TreeOid r)
+    treeEntry :: Tree r -> TreeFilePath -> m (Maybe (TreeEntry r))
+    sourceTreeEntries :: Tree r -> Producer m (TreeFilePath, TreeEntry r)
 
     diffContentsWithTree :: Source m (Either TreeFilePath ByteString)
-                         -> Tree m -> Producer m ByteString
+                         -> Tree r -> Producer m ByteString
 
     -- Creating other objects
-    hashContents :: BlobContents m -> m (BlobOid m)
-    createBlob   :: BlobContents m -> m (BlobOid m)
-    createCommit :: [CommitOid m] -> TreeOid m
+    hashContents :: BlobContents m -> m (BlobOid r)
+    createBlob   :: BlobContents m -> m (BlobOid r)
+    createCommit :: [CommitOid r] -> TreeOid r
                  -> Signature -> Signature -> CommitMessage -> Maybe RefName
-                 -> m (Commit m)
-    createTag :: CommitOid m -> Signature -> CommitMessage -> Text -> m (Tag m)
+                 -> m (Commit r)
+    createTag :: CommitOid r -> Signature -> CommitMessage -> Text -> m (Tag r)
 
     -- -- Pack files
-    -- buildPackFile :: FilePath -> [Either (CommitOid m) (TreeOid m)]
+    -- buildPackFile :: FilePath -> [Either (CommitOid r) (TreeOid r)]
     --               -> m FilePath
     -- buildPackFile _ _ =
     --     failure (BackendError "Backend does not support building pack files")
@@ -108,41 +110,36 @@
     , repoAutoCreate :: !Bool
     }
 
-instance Default RepositoryOptions where
-    def = RepositoryOptions "" True True
+defaultRepositoryOptions :: RepositoryOptions
+defaultRepositoryOptions = RepositoryOptions "" False False
 
-data RepositoryFactory t m c
-    = RepositoryFactory
-    { openRepository  :: RepositoryOptions -> m c
-    , runRepository   :: forall a. c -> t a -> m a
-    , closeRepository :: c -> m ()
-    , getRepository   :: t c
-    , defaultOptions  :: !RepositoryOptions
-    , startupBackend  :: m ()
-    , shutdownBackend :: m ()
+data RepositoryFactory n m r = RepositoryFactory
+    { openRepository :: RepositoryOptions -> m r
+    , runRepository  :: forall a. r -> n a -> m a
     }
 
 {- $oids -}
-class (Eq o, Ord o, Show o) => IsOid o where
+class IsOid o where
     renderOid :: o -> Text
     renderOid = renderObjOid . Tagged
+
     renderObjOid :: Tagged a o -> Text
     renderObjOid = renderOid . untag
 
-type BlobOid m   = Tagged (Blob m) (Oid m)
-type TreeOid m   = Tagged (Tree m) (Oid m)
-type CommitOid m = Tagged (Commit m) (Oid m)
-type TagOid m    = Tagged (Tag m) (Oid m)
+type BlobOid r   = Tagged r (Oid r)
+type TreeOid r   = Tagged (Tree r) (Oid r)
+type CommitOid r = Tagged (Commit r) (Oid r)
+type TagOid r    = Tagged (Tag r) (Oid r)
 
-data ObjectOid m = BlobObjOid   !(BlobOid m)
-                 | TreeObjOid   !(TreeOid m)
-                 | CommitObjOid !(CommitOid m)
-                 | TagObjOid    !(TagOid m)
+data ObjectOid r = BlobObjOid   !(BlobOid r)
+                 | TreeObjOid   !(TreeOid r)
+                 | CommitObjOid !(CommitOid r)
+                 | TagObjOid    !(TagOid r)
 
-parseObjOid :: Repository m => forall o. Text -> m (Tagged o (Oid m))
+parseObjOid :: MonadGit r m => forall o. Text -> m (Tagged o (Oid r))
 parseObjOid sha = Tagged <$> parseOid sha
 
-copyOid :: (Repository m, Repository n) => Oid m -> n (Oid n)
+copyOid :: (MonadGit r m, MonadGit s n) => Oid r -> n (Oid s)
 copyOid = parseOid . renderOid
 
 newtype SHA = SHA { getSHA :: ByteString } deriving (Eq, Ord, Read)
@@ -162,24 +159,23 @@
 instance Show SHA where
     show = T.unpack . shaToText
 
-instance Bin.Binary SHA where
-    put (SHA t) = Bin.put t
-    get = SHA <$> Bin.get
-
 instance Hashable SHA where
     hashWithSalt salt (SHA bs) = hashWithSalt salt bs
 
 {- $blobs -}
-data Blob m = Blob { blobOid      :: !(BlobOid m)
-                   , blobContents :: !(BlobContents m) }
+data Blob r m = Blob
+    { blobOid      :: !(BlobOid r)
+    , blobContents :: !(BlobContents m)
+    }
 
 type ByteSource m = Producer m ByteString
 
 data BlobContents m = BlobString !ByteString
+                    | BlobStringLazy !BL.ByteString
                     | BlobStream !(ByteSource m)
                     | BlobSizedStream !(ByteSource m) !Int
 
-data BlobKind = PlainBlob | ExecutableBlob | SymlinkBlob | UnknownBlob
+data BlobKind = PlainBlob | ExecutableBlob | SymlinkBlob
               deriving (Show, Eq, Enum)
 
 instance Eq (BlobContents m) where
@@ -187,125 +183,132 @@
   _ == _ = False
 
 {- $trees -}
-data TreeEntry m = BlobEntry   { blobEntryOid   :: !(BlobOid m)
+data TreeEntry r = BlobEntry   { blobEntryOid   :: !(BlobOid r)
                                , blobEntryKind  :: !BlobKind }
-                 | TreeEntry   { treeEntryOid   :: !(TreeOid m) }
-                 | CommitEntry { commitEntryOid :: !(CommitOid m) }
+                 | TreeEntry   { treeEntryOid   :: !(TreeOid r) }
+                 | CommitEntry { commitEntryOid :: !(CommitOid r) }
 
-instance Repository m => Show (TreeEntry m) where
-    show (BlobEntry oid _) = "<BlobEntry " ++ T.unpack (renderObjOid oid)
-    show (TreeEntry oid)   = "<TreeEntry " ++ T.unpack (renderObjOid oid)
-    show (CommitEntry oid) = "<CommitEntry " ++ T.unpack (renderObjOid oid)
+-- instance Show (TreeEntry r) where
+--     show (BlobEntry oid _) = "<BlobEntry " ++ show oid ++ ">"
+--     show (TreeEntry oid)   = "<TreeEntry " ++ show oid ++ ">"
+--     show (CommitEntry oid) = "<CommitEntry " ++ show oid ++ ">"
 
-treeEntryToOid :: Repository m => TreeEntry m -> Oid m
+treeEntryToOid :: MonadGit r m => TreeEntry r -> Oid r
 treeEntryToOid (BlobEntry boid _) = untag boid
 treeEntryToOid (TreeEntry toid)   = untag toid
 treeEntryToOid (CommitEntry coid) = untag coid
 
-data TreeBuilder m = TreeBuilder
-    { mtbBaseTreeOid    :: Maybe (TreeOid m)
-    , mtbPendingUpdates :: HashMap TreeFilePath (TreeBuilder m)
-    , mtbNewBuilder     :: Maybe (Tree m) -> m (TreeBuilder m)
-    , mtbWriteContents  :: TreeBuilder m -> m (ModifiedBuilder m, TreeOid m)
-    , mtbLookupEntry    :: TreeFilePath -> m (Maybe (TreeEntry m))
+data TreeBuilder r m = TreeBuilder
+    { mtbBaseTreeOid    :: Maybe (TreeOid r)
+    , mtbPendingUpdates :: HashMap TreeFilePath (TreeBuilder r m)
+    , mtbNewBuilder     :: Maybe (Tree r) -> m (TreeBuilder r m)
+    , mtbWriteContents  :: TreeBuilder r m -> m (ModifiedBuilder r m, TreeOid r)
+    , mtbLookupEntry    :: TreeFilePath -> m (Maybe (TreeEntry r))
     , mtbEntryCount     :: m Int
-    , mtbPutEntry       :: TreeBuilder m -> TreeFilePath -> TreeEntry m
-                        -> m (ModifiedBuilder m)
-    , mtbDropEntry      :: TreeBuilder m -> TreeFilePath
-                        -> m (ModifiedBuilder m)
+    , mtbPutEntry       :: TreeBuilder r m -> TreeFilePath -> TreeEntry r
+                        -> m (ModifiedBuilder r m)
+    , mtbDropEntry      :: TreeBuilder r m -> TreeFilePath
+                        -> m (ModifiedBuilder r m)
     }
 
-data ModifiedBuilder m = ModifiedBuilder (TreeBuilder m)
-                         | BuilderUnchanged (TreeBuilder m)
+data ModifiedBuilder r m = ModifiedBuilder (TreeBuilder r m)
+                         | BuilderUnchanged (TreeBuilder r m)
 
-instance Monoid (ModifiedBuilder m) where
+instance Semigroup (ModifiedBuilder r m) where
+    BuilderUnchanged _  <> BuilderUnchanged b2 = BuilderUnchanged b2
+    ModifiedBuilder b1  <> BuilderUnchanged _  = ModifiedBuilder b1
+    BuilderUnchanged _  <> ModifiedBuilder b2  = ModifiedBuilder b2
+    ModifiedBuilder _   <> ModifiedBuilder b2  = ModifiedBuilder b2
+
+instance Monoid (ModifiedBuilder r m) where
     mempty = BuilderUnchanged (error "ModifiedBuilder is a semigroup")
-    BuilderUnchanged _ `mappend` BuilderUnchanged b2 = BuilderUnchanged b2
-    ModifiedBuilder b1  `mappend` BuilderUnchanged _ = ModifiedBuilder b1
-    BuilderUnchanged _ `mappend` ModifiedBuilder b2  = ModifiedBuilder b2
-    ModifiedBuilder _  `mappend` ModifiedBuilder b2  = ModifiedBuilder b2
+    x `mappend` y = x <> y
 
-fromBuilderMod :: ModifiedBuilder m -> TreeBuilder m
+fromBuilderMod :: ModifiedBuilder r m -> TreeBuilder r m
 fromBuilderMod (BuilderUnchanged tb) = tb
 fromBuilderMod (ModifiedBuilder tb)  = tb
 
 {- $commits -}
-data Commit m = Commit
-    { commitOid       :: !(CommitOid m)
-    , commitParents   :: ![CommitOid m]
-    , commitTree      :: !(TreeOid m)
+data Commit r = Commit
+    { commitOid       :: !(CommitOid r)
+    , commitParents   :: ![CommitOid r]
+    , commitTree      :: !(TreeOid r)
     , commitAuthor    :: !Signature
     , commitCommitter :: !Signature
     , commitLog       :: !CommitMessage
     , commitEncoding  :: !Text
     }
 
-lookupCommitParents :: Repository m => Commit m -> m [Commit m]
-lookupCommitParents = mapM lookupCommit . commitParents
+sourceCommitParents :: MonadGit r m => Commit r -> Producer m (Commit r)
+sourceCommitParents commit =
+    forM_ (commitParents commit) $ yield <=< lift . lookupCommit
 
+lookupCommitParents :: MonadGit r m => Commit r -> m [Commit r]
+lookupCommitParents commit = sourceCommitParents commit $$ CL.consume
+
 data Signature = Signature
     { signatureName  :: !CommitAuthor
     , signatureEmail :: !CommitEmail
     , signatureWhen  :: !ZonedTime
     } deriving Show
 
-instance Default Signature where
-    def = Signature
-        { signatureName  = T.empty
-        , signatureEmail = T.empty
-        , signatureWhen  = ZonedTime
-            { zonedTimeToLocalTime = LocalTime
-                { localDay = ModifiedJulianDay 0
-                , localTimeOfDay = TimeOfDay 0 0 0
-                }
-            , zonedTimeZone = utc
+defaultSignature :: Signature
+defaultSignature = Signature
+    { signatureName  = T.empty
+    , signatureEmail = T.empty
+    , signatureWhen  = ZonedTime
+        { zonedTimeToLocalTime = LocalTime
+            { localDay = ModifiedJulianDay 0
+            , localTimeOfDay = TimeOfDay 0 0 0
             }
+        , zonedTimeZone = utc
         }
+    }
 
 {- $tags -}
-data Tag m = Tag
-    { tagOid    :: !(TagOid m)
-    , tagCommit :: !(CommitOid m)
+data Tag r = Tag
+    { tagOid    :: !(TagOid r)
+    , tagCommit :: !(CommitOid r)
     }
 
 {- $objects -}
-data Object m = BlobObj   !(Blob m)
-              | TreeObj   !(Tree m)
-              | CommitObj !(Commit m)
-              | TagObj    !(Tag m)
+data Object r m = BlobObj   !(Blob r m)
+                | TreeObj   !(Tree r)
+                | CommitObj !(Commit r)
+                | TagObj    !(Tag r)
 
-objectOid :: Repository m => Object m -> Oid m
-objectOid (BlobObj obj)   = untag (blobOid obj)
-objectOid (TreeObj obj)   = untag (treeOid obj)
-objectOid (CommitObj obj) = untag (commitOid obj)
-objectOid (TagObj obj)    = untag (tagOid obj)
+objectOid :: MonadGit r m => Object r m -> m (Oid r)
+objectOid (BlobObj obj)   = return $ untag (blobOid obj)
+objectOid (TreeObj obj)   = untag <$> treeOid obj
+objectOid (CommitObj obj) = return $ untag (commitOid obj)
+objectOid (TagObj obj)    = return $ untag (tagOid obj)
 
-loadObject :: Repository m => ObjectOid m -> m (Object m)
+loadObject :: MonadGit r m => ObjectOid r -> m (Object r m)
 loadObject (BlobObjOid oid)   = BlobObj   <$> lookupBlob oid
 loadObject (TreeObjOid oid)   = TreeObj   <$> lookupTree oid
 loadObject (CommitObjOid oid) = CommitObj <$> lookupCommit oid
 loadObject (TagObjOid oid)    = TagObj    <$> lookupTag oid
 
-objectToObjOid :: Repository m => Object m -> ObjectOid m
-objectToObjOid (BlobObj obj)   = BlobObjOid (blobOid obj)
-objectToObjOid (TreeObj obj)   = TreeObjOid (treeOid obj)
-objectToObjOid (CommitObj obj) = CommitObjOid (commitOid obj)
-objectToObjOid (TagObj obj)    = TagObjOid (tagOid obj)
+objectToObjOid :: MonadGit r m => Object r m -> m (ObjectOid r)
+objectToObjOid (BlobObj obj)   = return $ BlobObjOid (blobOid obj)
+objectToObjOid (TreeObj obj)   = TreeObjOid <$> treeOid obj
+objectToObjOid (CommitObj obj) = return $ CommitObjOid (commitOid obj)
+objectToObjOid (TagObj obj)    = return $ TagObjOid (tagOid obj)
 
-untagObjOid :: Repository m => ObjectOid m -> Oid m
+untagObjOid :: ObjectOid r -> Oid r
 untagObjOid (BlobObjOid oid)   = untag oid
 untagObjOid (TreeObjOid oid)   = untag oid
 untagObjOid (CommitObjOid oid) = untag oid
 untagObjOid (TagObjOid oid)    = untag oid
 
 {- $references -}
-data RefTarget m = RefObj !(Oid m) | RefSymbolic !RefName
+data RefTarget (r :: *) = RefObj !(Oid r) | RefSymbolic !RefName
 
-instance Repository m => Show (RefTarget m) where
-    show (RefObj oid) = "RefObj#" ++ T.unpack (renderOid oid)
-    show (RefSymbolic name) = "RefSymbolic#" ++ T.unpack name
+-- instance Show (RefTarget r) where
+--     show (RefObj oid) = "RefObj#" ++ T.unpack (renderOid oid)
+--     show (RefSymbolic name) = "RefSymbolic#" ++ T.unpack name
 
-commitRefTarget :: Commit m -> RefTarget m
+commitRefTarget :: Commit r -> RefTarget r
 commitRefTarget = RefObj . untag . commitOid
 
 {- $merges -}
@@ -356,37 +359,38 @@
 mergeStatus TypeChanged Deleted     = LeftTypeChangedRightDeleted
 mergeStatus TypeChanged TypeChanged = BothTypeChanged
 
-data MergeResult m
+data MergeResult r
     = MergeSuccess
-        { mergeCommit    :: CommitOid m
+        { mergeCommit    :: CommitOid r
         }
     | MergeConflicted
-        { mergeCommit    :: CommitOid m
-        , mergeHeadLeft  :: CommitOid m
-        , mergeHeadRight :: CommitOid m
+        { mergeCommit    :: CommitOid r
+        , mergeHeadLeft  :: CommitOid r
+        , mergeHeadRight :: CommitOid r
         , mergeConflicts ::
-               Map TreeFilePath (ModificationKind, ModificationKind)
+            Map TreeFilePath (ModificationKind, ModificationKind)
         }
 
-copyMergeResult :: (Repository m, MonadGit m, Repository n, MonadGit n)
-                => MergeResult m -> n (MergeResult n)
+copyMergeResult :: (MonadGit r m, IsOid (Oid s))
+                => MergeResult s -> m (MergeResult r)
 copyMergeResult (MergeSuccess mc) =
-    MergeSuccess <$> (Tagged <$> parseOid (renderObjOid mc))
+    MergeSuccess <$> parseObjOid (renderObjOid mc)
 copyMergeResult (MergeConflicted hl hr mc cs) =
-    MergeConflicted <$> (Tagged <$> parseOid (renderObjOid hl))
-                    <*> (Tagged <$> parseOid (renderObjOid hr))
-                    <*> (Tagged <$> parseOid (renderObjOid mc))
+    MergeConflicted <$> parseObjOid (renderObjOid hl)
+                    <*> parseObjOid (renderObjOid hr)
+                    <*> parseObjOid (renderObjOid mc)
                     <*> pure cs
 
-instance Repository m => Show (MergeResult m) where
-    show (MergeSuccess mc) = "MergeSuccess (" ++ show mc ++ ")"
-    show (MergeConflicted mc hl hr cs) =
-        "MergeResult"
-     ++ "\n    { mergeCommit    = " ++ show mc
-     ++ "\n    , mergeHeadLeft  = " ++ show hl
-     ++ "\n    , mergeHeadRight = " ++ show hr
-     ++ "\n    , mergeConflicts = " ++ show cs
-     ++ "\n    }"
+-- instance Show (MergeResult r) where
+--     show (MergeSuccess mc) =
+--         "MergeSuccess (" ++ T.unpack (renderObjOid mc) ++ ")"
+--     show (MergeConflicted mc hl hr cs) =
+--         "MergeResult"
+--      ++ "\n    { mergeCommit    = " ++ T.unpack (renderObjOid mc)
+--      ++ "\n    , mergeHeadLeft  = " ++ T.unpack (renderObjOid hl)
+--      ++ "\n    , mergeHeadRight = " ++ T.unpack (renderObjOid hr)
+--      ++ "\n    , mergeConflicts = " ++ show cs
+--      ++ "\n    }"
 
 {- $exceptions -}
 -- | There is a separate 'GitException' for each possible failure when
@@ -406,6 +410,7 @@
     | DiffTreeToIndexFailed Text
     | IndexAddFailed TreeFilePath Text
     | IndexCreateFailed Text
+    | PathEncodingError Text
     | PushNotFastForward Text
     | TranslationException Text
     | TreeCreateFailed Text
@@ -418,14 +423,14 @@
     | TreeCannotTraverseCommit
     | TreeEntryLookupFailed TreeFilePath
     | TreeUpdateFailed
-    | TreeWalkFailed
+    | TreeWalkFailed Text
     | TreeEmptyCreateFailed
     | CommitCreateFailed
     | CommitLookupFailed Text
     | ReferenceCreateFailed RefName
     | ReferenceDeleteFailed RefName
     | RefCannotCreateFromPartialOid
-    | ReferenceListingFailed
+    | ReferenceListingFailed Text
     | ReferenceLookupFailed RefName
     | ObjectLookupFailed Text Int
     | ObjectRefRequiresFullOid
diff --git a/Git/Utils.hs b/Git/Utils.hs
new file mode 100644
--- /dev/null
+++ b/Git/Utils.hs
@@ -0,0 +1,1 @@
+module Git.Utils where
diff --git a/Git/Working.hs b/Git/Working.hs
new file mode 100644
--- /dev/null
+++ b/Git/Working.hs
@@ -0,0 +1,62 @@
+{-# LANGUAGE CPP #-}
+
+module Git.Working where
+
+import Control.Applicative
+import Control.Failure
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Resource
+import Data.Conduit
+import Data.Conduit.List as CL
+import Data.Text as T
+import Data.Semigroup
+import Git.Blob
+import Git.Types
+import System.Directory
+import System.FilePath
+#if !mingw32_HOST_OS
+import System.Posix.Files
+#endif
+
+checkoutFiles :: (MonadGit r m, MonadBaseControl IO m, MonadIO m,
+                  MonadResource m)
+              => FilePath
+              -> Tree r
+              -> (TreeFilePath -> Either String FilePath)
+              -> Bool
+              -> m ()
+checkoutFiles destPath tree decode cloneSubmodules =
+    sourceTreeEntries tree $$ CL.mapM_ $ \(path, entry) ->
+        case (destPath </>) <$> decode path of
+            Left e ->  decodeError path e
+            Right fullPath -> do
+                liftIO $ createDirectoryIfMissing True (takeDirectory fullPath)
+                case entry of
+                    TreeEntry {} -> return ()
+                    BlobEntry oid kind -> checkoutBlob oid kind fullPath
+                    CommitEntry oid
+                        -- jww (2013-12-26): Recursively clone submodules?
+                        | cloneSubmodules -> cloneSubmodule oid fullPath
+                        | otherwise -> liftIO $ createDirectory fullPath
+  where
+    decodeError path e = failure $ PathEncodingError $
+        "Could not decode path " <> T.pack (show path) <> ":" <> T.pack e
+
+    checkoutBlob oid kind fullPath = do
+        Blob _ contents <- lookupBlob oid
+        case kind of
+#if !mingw32_HOST_OS
+            SymlinkBlob -> do
+                target <- blobContentsToByteString contents
+                case decode target of
+                    Left e -> decodeError target e
+                    Right targetPath ->
+                        liftIO $ createSymbolicLink targetPath fullPath
+#endif
+            _ -> do                  -- PlainBlob | ExecutableBlob
+                -- jww (2013-12-26): There is no way to know what a tree's
+                -- path has been encoded as.
+                writeBlob fullPath contents
+
+    cloneSubmodule =
+        error "jww (2013-12-29): Cloning submodule is not yet implemented"
diff --git a/gitlib.cabal b/gitlib.cabal
--- a/gitlib.cabal
+++ b/gitlib.cabal
@@ -1,5 +1,5 @@
 Name:                gitlib
-Version:             2.2.0.0
+Version:             3.0.0
 Synopsis:            API library for working with Git repositories
 License-file:        LICENSE
 License:             MIT
@@ -41,37 +41,47 @@
     build-depends:
           base                 >= 3 && < 5
         , base16-bytestring    >= 0.1.1.5
-        , binary               >= 0.5.1.0
         , bytestring           >= 0.9.2.1
         , conduit              >= 1.0.0
         , containers           >= 0.4.2.1
-        , data-default         >= 0.5.0
         , directory            >= 1.1.0.2
         , failure              >= 0.2.0.1
+        , filepath             >= 1.3.0.0
         , hashable             >= 1.1.2.5
         , lifted-base          >= 0.2
+        , monad-control        >= 0.3.2.1
+        , mtl                  >= 2.1.2
+        , resourcet            >= 0.4.8
+        , semigroups           >= 0.11
         , tagged               >= 0.2.3.1
         , text                 >= 0.11.2
         , time                 >= 1.4
         , transformers         >= 0.3.0.0
         , unordered-containers >= 0.2.3.0
+    if !os(mingw32)
+        build-depends:
+          unix                 >= 2.5.1.1
     exposed-modules:
         Git
-        Git.Types
         Git.Blob
-        Git.Tree
-        Git.Tree.Builder
-        Git.Tree.Builder.Pure
         Git.Commit
         Git.Commit.Push
         Git.Object
         Git.Reference
         Git.Repository
+        Git.Tree
+        Git.Tree.Builder
+        Git.Tree.Builder.Pure
         Git.Tutorial
+        Git.Types
+        Git.Utils
+        Git.Working
     default-extensions: 
         ConstraintKinds
         DeriveDataTypeable
         FlexibleContexts
+        FunctionalDependencies
+        MultiParamTypeClasses
         OverloadedStrings
         RankNTypes
         TypeFamilies
