diff --git a/Data/Git/Blob.hs b/Data/Git/Blob.hs
--- a/Data/Git/Blob.hs
+++ b/Data/Git/Blob.hs
@@ -1,8 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 module Data.Git.Blob
-       ( Blob(..), HasBlob(..)
+       ( Blob(..)
        , newBlobBase
        , createBlob
        , getBlobContents
@@ -18,26 +17,24 @@
 
 default (Text)
 
-data Blob = Blob { _blobInfo     :: Base Blob
-                 , _blobContents :: B.ByteString }
-
-makeClassy ''Blob
+data Blob = Blob { blobInfo     :: Base Blob
+                 , blobContents :: B.ByteString }
 
 instance Show Blob where
-  show x = case x^.blobInfo.gitId of
+  show x = case gitId (blobInfo x) of
     Pending _ -> "Blob"
     Stored y  -> "Blob#" ++ show y
 
 instance Updatable Blob where
-  getId x        = x^.blobInfo.gitId
-  objectRepo x   = x^.blobInfo.gitRepo
-  objectPtr x    = x^.blobInfo.gitObj
+  getId x        = gitId (blobInfo x)
+  objectRepo x   = gitRepo (blobInfo x)
+  objectPtr x    = gitObj (blobInfo x)
   update         = writeBlob
   lookupFunction = lookupBlob
 
 newBlobBase :: Blob -> Base Blob
 newBlobBase b =
-  newBase (b^.blobInfo.gitRepo) (Pending doWriteBlob) Nothing
+  newBase (gitRepo (blobInfo b)) (Pending doWriteBlob) Nothing
 
 -- | Create a new blob in the 'Repository', with 'ByteString' as its contents.
 --
@@ -47,32 +44,32 @@
 createBlob text repo
   | text == B.empty = error "Cannot create an empty blob"
   | otherwise =
-    Blob { _blobInfo     = newBase repo (Pending doWriteBlob) Nothing
-         , _blobContents = text }
+    Blob { blobInfo     = newBase repo (Pending doWriteBlob) Nothing
+         , blobContents = text }
 
 lookupBlob :: Oid -> Repository -> IO (Maybe Blob)
 lookupBlob oid repo =
   lookupObject' oid repo c'git_blob_lookup c'git_blob_lookup_prefix $
     \coid obj _ ->
-      return Blob { _blobInfo     = newBase repo (Stored coid) (Just obj)
-                  , _blobContents = B.empty }
+      return Blob { blobInfo     = newBase repo (Stored coid) (Just obj)
+                  , blobContents = B.empty }
 
 getBlobContents :: Blob -> IO (Blob, B.ByteString)
 getBlobContents b =
-  case b^.blobInfo.gitId of
-    Pending _   -> return $ (b, contents)
+  case gitId (blobInfo b) of
+    Pending _   -> return (b, contents)
     Stored hash ->
       if contents /= B.empty
         then return (b, contents)
         else
-        case b^.blobInfo.gitObj of
+        case gitObj (blobInfo b) of
           Just blobPtr ->
             withForeignPtr blobPtr $ \ptr -> do
               size <- c'git_blob_rawsize (castPtr ptr)
               buf  <- c'git_blob_rawcontent (castPtr ptr)
               bstr <- curry unsafePackCStringLen (castPtr buf)
                             (fromIntegral size)
-              return (blobContents .~ bstr $ b, bstr)
+              return (b { blobContents = bstr }, bstr)
 
           Nothing -> do
             b' <- lookupBlob (Oid hash) repo
@@ -80,16 +77,16 @@
               Just blobPtr' -> getBlobContents blobPtr'
               Nothing       -> return (b, B.empty)
 
-  where repo     = b^.blobInfo.gitRepo
-        contents = b^.blobContents
+  where repo     = gitRepo (blobInfo b)
+        contents = blobContents b
 
 -- | Write out a blob to its repository.  If it has already been written,
 --   nothing will happen.
 writeBlob :: Blob -> IO Blob
-writeBlob b@(Blob { _blobInfo = Base { _gitId = Stored _ } }) = return b
+writeBlob b@(Blob { blobInfo = Base { gitId = Stored _ } }) = return b
 writeBlob b = do hash <- doWriteBlob b
-                 return $ blobInfo.gitId .~ Stored hash $
-                          blobContents   .~ B.empty    $ b
+                 return b { blobInfo     = (blobInfo b) { gitId = Stored hash }
+                          , blobContents = B.empty }
 
 doWriteBlob :: Blob -> IO COid
 doWriteBlob b = do
@@ -100,10 +97,10 @@
 
   where
     repo = fromMaybe (error "Repository invalid")
-                     (b^.blobInfo.gitRepo.repoObj)
+                     (repoObj (gitRepo (blobInfo b)))
 
     createFromBuffer ptr repoPtr =
-      unsafeUseAsCStringLen (b^.blobContents) $
+      unsafeUseAsCStringLen (blobContents b) $
         uncurry (\cstr len ->
                   withForeignPtr ptr $ \ptr' ->
                     c'git_blob_create_frombuffer
diff --git a/Data/Git/Commit.hs b/Data/Git/Commit.hs
--- a/Data/Git/Commit.hs
+++ b/Data/Git/Commit.hs
@@ -1,6 +1,5 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 module Data.Git.Commit where
 
@@ -20,32 +19,30 @@
 
 default (Text)
 
-data Commit = Commit { _commitInfo      :: Base Commit
-                     , _commitAuthor    :: Signature
-                     , _commitCommitter :: Signature
-                     , _commitLog       :: Text
-                     , _commitEncoding  :: Prelude.String
-                     , _commitTree      :: ObjRef Tree
-                     , _commitParents   :: [ObjRef Commit]
-                     , _commitObj       :: ObjPtr C'git_commit }
-
-makeClassy ''Commit
+data Commit = Commit { commitInfo      :: Base Commit
+                     , commitAuthor    :: Signature
+                     , commitCommitter :: Signature
+                     , commitLog       :: Text
+                     , commitEncoding  :: Prelude.String
+                     , commitTree      :: ObjRef Tree
+                     , commitParents   :: [ObjRef Commit]
+                     , commitObj       :: ObjPtr C'git_commit }
 
 instance Show Commit where
-  show x = case x^.commitInfo.gitId of
+  show x = case gitId (commitInfo x) of
     Pending _ -> "Commit"
     Stored y  -> "Commit#" ++ show y
 
 instance Updatable Commit where
-  getId x        = x^.commitInfo.gitId
-  objectRepo x   = x^.commitInfo.gitRepo
-  objectPtr x    = x^.commitInfo.gitObj
+  getId x        = gitId (commitInfo x)
+  objectRepo x   = gitRepo (commitInfo x)
+  objectPtr x    = gitObj (commitInfo x)
   update         = writeCommit Nothing
   lookupFunction = lookupCommit
 
 newCommitBase :: Commit -> Base Commit
 newCommitBase t =
-  newBase (t^.commitInfo.gitRepo)
+  newBase (gitRepo (commitInfo t))
           (Pending (doWriteCommit Nothing >=> return . snd)) Nothing
 
 -- | Create a new, empty commit.
@@ -54,16 +51,16 @@
 --   commit is a no-op.
 createCommit :: Repository -> Commit
 createCommit repo =
-  Commit { _commitInfo     =
-            newBase repo (Pending (doWriteCommit Nothing >=> return . snd))
-                    Nothing
-         , _commitAuthor    = createSignature
-         , _commitCommitter = createSignature
-         , _commitTree      = ObjRef (createTree repo)
-         , _commitParents   = []
-         , _commitLog       = T.empty
-         , _commitEncoding  = ""
-         , _commitObj       = Nothing }
+  Commit { commitInfo     =
+           newBase repo (Pending (doWriteCommit Nothing >=> return . snd))
+                   Nothing
+         , commitAuthor    = createSignature
+         , commitCommitter = createSignature
+         , commitTree      = ObjRef (createTree repo)
+         , commitParents   = []
+         , commitLog       = T.empty
+         , commitEncoding  = ""
+         , commitObj       = Nothing }
 
 lookupCommit :: Oid -> Repository -> IO (Maybe Commit)
 lookupCommit oid repo =
@@ -90,19 +87,19 @@
                                               (c'git_commit_parent_oid c))
                                    [0..pn])
 
-        return Commit { _commitInfo      = newBase repo (Stored coid) (Just obj)
-                      , _commitAuthor    = auth
-                      , _commitCommitter = comm
-                      , _commitTree      = toid
-                      , _commitParents   = poids
-                      , _commitLog       = U.toUnicode conv msg
-                      , _commitEncoding  = encs
-                      , _commitObj       = Just $ unsafeCoerce obj }
+        return Commit { commitInfo      = newBase repo (Stored coid) (Just obj)
+                      , commitAuthor    = auth
+                      , commitCommitter = comm
+                      , commitTree      = toid
+                      , commitParents   = poids
+                      , commitLog       = U.toUnicode conv msg
+                      , commitEncoding  = encs
+                      , commitObj       = Just $ unsafeCoerce obj }
 
 -- | Write out a commit to its repository.  If it has already been written,
 --   nothing will happen.
 writeCommit :: Maybe Text -> Commit -> IO Commit
-writeCommit _ c@(Commit { _commitInfo = Base { _gitId = Stored _ } }) =
+writeCommit _ c@(Commit { commitInfo = Base { gitId = Stored _ } }) =
   return c
 writeCommit ref c = fst <$> doWriteCommit ref c
 
@@ -111,29 +108,30 @@
   coid <- withForeignPtr repo $ \repoPtr -> do
     coid <- mallocForeignPtr
     withForeignPtr coid $ \coid' -> do
-      conv <- U.open (c^.commitEncoding) (Just True)
-      BS.useAsCString (U.fromUnicode conv (c^.commitLog)) $ \message ->
+      conv <- U.open (commitEncoding c) (Just True)
+      BS.useAsCString (U.fromUnicode conv (commitLog c)) $ \message ->
         withRef ref $ \update_ref ->
-          withSignature conv (c^.commitAuthor) $ \author ->
-            withSignature conv (c^.commitCommitter) $ \committer ->
-              withEncStr (c^.commitEncoding) $ \message_encoding ->
-                withGitTree (c^.commitTree) c $ \commit_tree -> do
+          withSignature conv (commitAuthor c) $ \author ->
+            withSignature conv (commitCommitter c) $ \committer ->
+              withEncStr (commitEncoding c) $ \message_encoding ->
+                withGitTree (commitTree c) c $ \commit_tree -> do
                   parentPtrs <- getCommitParentPtrs c
                   parents    <- newArray $
                                map FU.unsafeForeignPtrToPtr parentPtrs
                   r <- c'git_commit_create coid' repoPtr
                         update_ref author committer
                         message_encoding message commit_tree
-                        (fromIntegral (length (c^.commitParents)))
+                        (fromIntegral (length (commitParents c)))
                         parents
                   when (r < 0) $ throwIO CommitCreateFailed
                   return coid
 
-  return (commitInfo.gitId .~ Stored (COid coid) $ c, COid coid)
+  return (c { commitInfo = (commitInfo c) { gitId = Stored (COid coid) } }
+         , COid coid)
 
   where
     repo = fromMaybe (error "Repository invalid")
-                     (c^.commitInfo.gitRepo.repoObj)
+                     (repoObj (gitRepo (commitInfo c)))
 
     withRef refName =
       if isJust refName
@@ -151,14 +149,14 @@
                      case parent of
                        Nothing -> error "Cannot find Git commit"
                        Just p' -> return p')
-           (c^.commitParents)
+           (commitParents c)
 
 getCommitParentPtrs :: Commit -> IO [ForeignPtr C'git_commit]
 getCommitParentPtrs c =
   withForeignPtr (repositoryPtr (objectRepo c)) $ \repoPtr ->
-    for (c^.commitParents) $ \p ->
+    for (commitParents c) $ \p ->
       case p of
-        ObjRef (Commit { _commitObj = Just obj }) -> return obj
+        ObjRef (Commit { commitObj = Just obj }) -> return obj
         _ -> do
           Oid (COid oid) <-
             case p of
@@ -176,27 +174,27 @@
   :: FilePath -> (Maybe TreeEntry -> Either a (Maybe TreeEntry)) -> Bool
   -> Commit -> IO (Either a Commit)
 modifyCommitTree path f createIfNotExist c =
-  withObject (c^.commitTree) c $ \tr -> do
+  withObject (commitTree c) c $ \tr -> do
     result <- modifyTree path f createIfNotExist tr
     case result of
       Left x    -> return (Left x)
-      Right tr' -> return $ Right $ commitTree .~ ObjRef tr' $ c
+      Right tr' -> return $ Right $ c { commitTree = ObjRef tr' }
 
 removeFromCommitTree :: FilePath -> Commit -> IO Commit
 removeFromCommitTree path c =
-  withObject (c^.commitTree) c $ \tr -> do
+  withObject (commitTree c) c $ \tr -> do
     tr' <- removeFromTree path tr
-    return $ commitTree .~ ObjRef tr' $ c
+    return c { commitTree = ObjRef tr' }
 
 doUpdateCommit :: [Text] -> TreeEntry -> Commit -> IO Commit
 doUpdateCommit xs item c = do
-  t <- loadObject (c^.commitTree) c
+  t <- loadObject (commitTree c) c
   case t of
     Nothing -> error "Failed to load tree for commit"
     Just t' -> do
       tr <- doModifyTree xs (const (Right (Just item))) True t'
       case tr of
-        Right tr' -> return $ commitTree .~ ObjRef tr' $ c
+        Right tr' -> return c { commitTree = ObjRef tr' }
         _ -> undefined
 
 updateCommit :: FilePath -> TreeEntry -> Commit -> IO Commit
diff --git a/Data/Git/Common.hs b/Data/Git/Common.hs
--- a/Data/Git/Common.hs
+++ b/Data/Git/Common.hs
@@ -1,16 +1,15 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 module Data.Git.Common
-       ( Signature(..), HasSignature(..)
+       ( Signature(..)
        , createSignature
        , packSignature
        , withSignature
-       , Base(..), gitId, gitRepo
+
+       , Base(..)
        , newBase )
        where
 
-import           Control.Lens
 import qualified Data.ByteString as BS
 import           Data.Git.Internal
 import qualified Data.Text as T
@@ -22,13 +21,11 @@
 
 default (Text)
 
-data Signature = Signature { _signatureName  :: Text
-                           , _signatureEmail :: Text
-                           , _signatureWhen  :: UTCTime }
+data Signature = Signature { signatureName  :: Text
+                           , signatureEmail :: Text
+                           , signatureWhen  :: UTCTime }
                deriving (Show, Eq)
 
-makeClassy ''Signature
-
 -- | Convert a time in seconds (from Stripe's servers) to 'UTCTime'. See
 --   "Data.Time.Format" for more on working with 'UTCTime'.
 fromSeconds :: Integer -> UTCTime
@@ -50,9 +47,9 @@
 
 createSignature :: Signature
 createSignature =
-  Signature { _signatureName  = T.empty
-            , _signatureEmail = T.empty
-            , _signatureWhen  =
+  Signature { signatureName  = T.empty
+            , signatureEmail = T.empty
+            , signatureWhen  =
               UTCTime { utctDay =
                            ModifiedJulianDay {
                              toModifiedJulianDay = 0 }
@@ -64,18 +61,18 @@
   email <- peek (p'git_signature'email sig) >>= BS.packCString
   time  <- peekGitTime (p'git_signature'when sig)
   return $
-    Signature { _signatureName  = U.toUnicode conv name
-              , _signatureEmail = U.toUnicode conv email
-              , _signatureWhen  = time }
+    Signature { signatureName  = U.toUnicode conv name
+              , signatureEmail = U.toUnicode conv email
+              , signatureWhen  = time }
 
 withSignature :: U.Converter -> Signature
               -> (Ptr C'git_signature -> IO a) -> IO a
 withSignature conv sig f =
-  BS.useAsCString (U.fromUnicode conv (sig^.signatureName)) $ \nameCStr ->
-    BS.useAsCString (U.fromUnicode conv (sig^.signatureEmail)) $ \emailCStr ->
+  BS.useAsCString (U.fromUnicode conv (signatureName sig)) $ \nameCStr ->
+    BS.useAsCString (U.fromUnicode conv (signatureEmail sig)) $ \emailCStr ->
       alloca $ \ptr -> do
         poke ptr (C'git_signature nameCStr emailCStr
-                                  (packGitTime (sig^.signatureWhen)))
+                                  (packGitTime (signatureWhen sig)))
         f ptr
 
 -- Common.hs
diff --git a/Data/Git/Internal.hs b/Data/Git/Internal.hs
--- a/Data/Git/Internal.hs
+++ b/Data/Git/Internal.hs
@@ -1,6 +1,5 @@
 {-# OPTIONS_HADDOCK hide #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE TypeSynonymInstances #-}
 {-# LANGUAGE FlexibleInstances #-}
 
@@ -9,12 +8,9 @@
 
        , Updatable(..)
 
-       , Base(..), gitId, gitRepo, gitObj
-       , newBase
-
-       , Repository
-       , HasRepository(..)
+       , Base(..), newBase
 
+       , Repository(..)
        , openRepository
        , createRepository
        , openOrCreateRepository
@@ -31,7 +27,6 @@
 import           Control.Applicative as X
 import           Control.Category as X
 import           Control.Exception as X
-import           Control.Lens as X
 import           Control.Monad as X hiding (mapM, mapM_, sequence, sequence_,
                                   forM, forM_, msum)
 import           Data.Bool as X
@@ -85,6 +80,13 @@
     Pending f -> Oid <$> f x
     Stored y  -> return $ Oid y
 
+  objectRef :: a -> IO (ObjRef a)
+  objectRef x = do
+    oid <- objectId x
+    case oid of
+      Oid coid -> return (IdRef coid)
+      PartialOid _ _ -> error "Did not expect to see a PartialOid"
+
   maybeObjectId :: a -> Maybe Oid
   maybeObjectId x = case getId x of
     Pending _ -> Nothing
@@ -100,32 +102,28 @@
   getObject (IdRef _)  = Nothing
   getObject (ObjRef x) = Just x
 
-data Repository = Repository { _repoPath :: FilePath
-                             , _repoObj  :: ObjPtr C'git_repository }
-
-makeClassy ''Repository
+data Repository = Repository { repoPath :: FilePath
+                             , repoObj  :: ObjPtr C'git_repository }
 
 instance Show Repository where
-  show x = "Repository " <> toString (x^.repoPath)
+  show x = "Repository " <> toString (repoPath x)
 
-data Base a = Base { _gitId   :: Ident a
-                   , _gitRepo :: Repository
-                   , _gitObj  :: ObjPtr C'git_object }
-
-makeLenses ''Base
+data Base a = Base { gitId   :: Ident a
+                   , gitRepo :: Repository
+                   , gitObj  :: ObjPtr C'git_object }
 
 instance Show (Base a) where
-  show x = case x^.gitId of
+  show x = case gitId x of
     Pending _ -> "Base"
     Stored y  -> "Base#" ++ show y
 
 newBase :: Repository -> Ident a -> ObjPtr C'git_object -> Base a
-newBase repo oid obj = Base { _gitId   = oid
-                            , _gitRepo = repo
-                            , _gitObj  = obj }
+newBase repo oid obj = Base { gitId   = oid
+                            , gitRepo = repo
+                            , gitObj  = obj }
 
 repositoryPtr :: Repository -> ForeignPtr C'git_repository
-repositoryPtr repo = fromMaybe (error "Repository invalid") (repo^.repoObj)
+repositoryPtr repo = fromMaybe (error "Repository invalid") (repoObj repo)
 
 openRepository :: FilePath -> IO Repository
 openRepository path =
@@ -154,8 +152,8 @@
         when (r < 0) $ doesNotExist p
         ptr' <- peek ptr
         fptr <- newForeignPtr p'git_repository_free ptr'
-        return Repository { _repoPath = path
-                          , _repoObj  = Just fptr }
+        return Repository { repoPath = path
+                          , repoObj  = Just fptr }
 
   where doesNotExist = throwIO . RepositoryNotExist . toString
 
diff --git a/Data/Git/Object.hs b/Data/Git/Object.hs
--- a/Data/Git/Object.hs
+++ b/Data/Git/Object.hs
@@ -48,14 +48,14 @@
              -> IO Object
 createObject coid obj repo typ
   | typ == c'GIT_OBJ_BLOB =
-    return $ BlobObj Blob { _blobInfo =
-                               newBase repo (Stored coid) (Just obj)
-                          , _blobContents = B.empty }
+    return $ BlobObj Blob { blobInfo =
+                              newBase repo (Stored coid) (Just obj)
+                          , blobContents = B.empty }
 
   | typ == c'GIT_OBJ_TREE =
-    return $ TreeObj Tree { _treeInfo =
-                               newBase repo (Stored coid) (Just obj)
-                          , _treeContents = M.empty }
+    return $ TreeObj Tree { treeInfo =
+                              newBase repo (Stored coid) (Just obj)
+                          , treeContents = M.empty }
 
   | otherwise = return undefined
 
diff --git a/Data/Git/Oid.hs b/Data/Git/Oid.hs
--- a/Data/Git/Oid.hs
+++ b/Data/Git/Oid.hs
@@ -56,6 +56,10 @@
 data Ident a = Pending (a -> IO COid)
              | Stored COid
 
+instance Show (Ident a) where
+  show (Pending _) = "Pending"
+  show (Stored coid) = show coid
+
 -- | 'Oid' represents either a full or partial SHA1 hash code used to identify
 --   Git objects.
 data Oid = Oid COid
diff --git a/Data/Git/Reference.hs b/Data/Git/Reference.hs
--- a/Data/Git/Reference.hs
+++ b/Data/Git/Reference.hs
@@ -1,13 +1,13 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 module Data.Git.Reference
        ( RefTarget(..)
-       , Reference(..), HasReference(..)
-
+       , Reference(..)
        , createRef
        , lookupRef
+       , listRefNames
+       , listAll
        , lookupId
        , writeRef
        , writeRef_ )
@@ -18,6 +18,9 @@
 import           Data.Git.Internal
 import           Data.Git.Object
 import qualified Prelude
+import           Prelude ((+),(-))
+import qualified Data.Text as T
+import           Foreign.Marshal.Array
 
 default (Text)
 
@@ -25,23 +28,21 @@
                | RefTargetSymbolic Text
                deriving (Show, Eq)
 
-data Reference = Reference { _refRepo   :: Repository
-                           , _refName   :: Text
-                           , _refTarget :: RefTarget
-                           , _refObj    :: ObjPtr C'git_reference }
+data Reference = Reference { refRepo   :: Repository
+                           , refName   :: Text
+                           , refTarget :: RefTarget
+                           , refObj    :: ObjPtr C'git_reference }
                deriving Show
 
-makeClassy ''Reference
-
 -- int git_reference_lookup(git_reference **reference_out,
 --   git_repository *repo, const char *name)
 
 createRef :: Text -> RefTarget -> Repository -> Reference
 createRef name target repo =
-  Reference { _refRepo   = repo
-            , _refName   = name
-            , _refTarget = target
-            , _refObj    = Nothing }
+  Reference { refRepo   = repo
+            , refName   = name
+            , refTarget = target
+            , refObj    = Nothing }
 
 lookupRef :: Text -> Repository -> IO (Maybe Reference)
 lookupRef name repo = alloca $ \ptr -> do
@@ -53,16 +54,16 @@
     else do
     ref  <- peek ptr
     fptr <- newForeignPtr p'git_reference_free ref
-    return $ Just $ Reference { _refRepo   = repo
-                              , _refName   = name
-                              , _refTarget = undefined
-                              , _refObj    = Just fptr }
+    return $ Just $ Reference { refRepo   = repo
+                              , refName   = name
+                              , refTarget = undefined
+                              , refObj    = Just fptr }
 
 writeRef :: Reference -> IO Reference
 writeRef ref = alloca $ \ptr -> do
   withForeignPtr repo $ \repoPtr ->
-    withCStringable (ref^.refName) $ \namePtr -> do
-      r <- case ref^.refTarget of
+    withCStringable (refName ref) $ \namePtr -> do
+      r <- case refTarget ref of
         RefTargetId (PartialOid {}) ->
           throwIO RefCannotCreateFromPartialOid
 
@@ -78,10 +79,10 @@
       when (r < 0) $ throwIO ReferenceCreateFailed
 
   fptr <- newForeignPtr_ =<< peek ptr
-  return $ refObj .~ Just fptr $ ref
+  return ref { refObj = Just fptr }
 
   where
-    repo = fromMaybe (error "Repository invalid") (ref^.refRepo.repoObj)
+    repo = fromMaybe (error "Repository invalid") (repoObj (refRepo ref))
 
 writeRef_ :: Reference -> IO ()
 writeRef_ = void . writeRef
@@ -98,7 +99,7 @@
       Oid <$> COid <$> newForeignPtr_ ptr
 
   where
-    repo = fromMaybe (error "Repository invalid") (repos^.repoObj)
+    repo = fromMaybe (error "Repository invalid") (repoObj repos)
 
 -- int git_reference_create_symbolic(git_reference **ref_out,
 --   git_repository *repo, const char *name, const char *target, int force)
@@ -151,8 +152,43 @@
 
 --packallRefs = c'git_reference_packall
 
--- int git_reference_list(git_strarray *array, git_repository *repo,
---   unsigned int list_flags)
+data ListFlags = ListFlags { invalid  :: Bool
+                           , oid      :: Bool
+                           , symbolic :: Bool
+                           , packed   :: Bool
+                           , hasPeel  :: Bool }
+               deriving Show
+
+listAll :: ListFlags
+listAll = ListFlags { invalid  = False
+                    , oid      = True
+                    , symbolic = True
+                    , packed   = True
+                    , hasPeel  = False }
+
+gitStrArray2List :: Ptr C'git_strarray -> IO [ Text ]
+gitStrArray2List gitStrs = do
+  count <- fromIntegral <$> ( peek $ p'git_strarray'count gitStrs )
+  strings <- peek $ p'git_strarray'strings gitStrs
+
+  r0 <- Foreign.Marshal.Array.peekArray count strings
+  r1 <- sequence $ fmap peekCString r0
+  return $ fmap T.pack r1
+
+listRefNames :: Repository -> ListFlags -> IO [ Text ]
+listRefNames repo flags =
+  let intFlags = (if oid flags then 1 else 0)
+               + (if symbolic flags then 2 else 0)
+               + (if packed flags then 4 else 0)
+               + (if hasPeel flags then 8 else 0)
+  in alloca $ \c'refs ->
+    withForeignPtr (repositoryPtr repo) $ \repoPtr -> do
+      r <- c'git_reference_list c'refs repoPtr intFlags
+      when (r < 0) $ throwIO ReferenceLookupFailed
+
+      refs <- gitStrArray2List c'refs
+      c'git_strarray_free c'refs
+      return refs
 
 --listRefs = c'git_reference_list
 
diff --git a/Data/Git/Repository.hs b/Data/Git/Repository.hs
--- a/Data/Git/Repository.hs
+++ b/Data/Git/Repository.hs
@@ -1,5 +1,4 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 -- | Interface for opening and creating repositories.  Repository objects are
 --   immutable, and serve only to refer to the given repository.  Any data
@@ -11,8 +10,6 @@
        , Updatable(..)
 
        , Repository
-       , HasRepository(..)
-
        , openRepository
        , createRepository
        , openOrCreateRepository
diff --git a/Data/Git/Tag.hs b/Data/Git/Tag.hs
--- a/Data/Git/Tag.hs
+++ b/Data/Git/Tag.hs
@@ -1,9 +1,7 @@
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 
 module Data.Git.Tag where
 
-import           Control.Lens
 import           Data.Git.Common
 import           Data.Git.Internal
 import qualified Data.Text as T
@@ -11,13 +9,11 @@
 
 default (Text)
 
-data Tag = Tag { _tagInfo :: Base Tag
-               , _tagRef  :: Oid }
-
-makeClassy ''Tag
+data Tag = Tag { tagInfo :: Base Tag
+               , tagRef  :: Oid }
 
 instance Show Tag where
-  show x = case x^.tagInfo.gitId of
+  show x = case gitId (tagInfo x) of
     Pending _ -> "Tag"
     Stored y  -> "Tag#" ++ show y
 
diff --git a/Data/Git/Tree.hs b/Data/Git/Tree.hs
--- a/Data/Git/Tree.hs
+++ b/Data/Git/Tree.hs
@@ -1,12 +1,12 @@
 {-# LANGUAGE DeriveDataTypeable #-}
 {-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TemplateHaskell #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE FlexibleContexts #-}
 
 module Data.Git.Tree where
 
 import           Bindings.Libgit2
+import           Control.Concurrent.ParallelIO
 import           Data.Git.Blob
 import           Data.Git.Common
 import           Data.Git.Errors
@@ -49,26 +49,28 @@
 
 type TreeMap = M.Map Text TreeEntry
 
-data Tree = Tree { _treeInfo     :: Base Tree
-                 , _treeContents :: TreeMap }
-
-makeClassy ''Tree
+data Tree = Tree { treeInfo     :: Base Tree
+                 , treeContents :: TreeMap }
 
 instance Show Tree where
-  show x = case x^.treeInfo.gitId of
+  show x = case gitId (treeInfo x) of
     Pending _ -> "Tree"
     Stored y  -> "Tree#" ++ show y
 
+instance Show TreeEntry where
+  show be@(BlobEntry {}) = show be
+  show te@(TreeEntry {}) = show te
+
 instance Updatable Tree where
-  getId x        = x^.treeInfo.gitId
-  objectRepo x   = x^.treeInfo.gitRepo
-  objectPtr x    = x^.treeInfo.gitObj
+  getId x        = gitId (treeInfo x)
+  objectRepo x   = gitRepo (treeInfo x)
+  objectPtr x    = gitObj (treeInfo x)
   update         = writeTree
   lookupFunction = lookupTree
 
 newTreeBase :: Tree -> Base Tree
 newTreeBase t =
-  newBase (t^.treeInfo.gitRepo)
+  newBase (gitRepo (treeInfo t))
           (Pending (doWriteTree >=> return . snd)) Nothing
 
 -- | Create a new, empty tree.
@@ -77,26 +79,51 @@
 --   tree is a no-op.
 createTree :: Repository -> Tree
 createTree repo =
-  Tree { _treeInfo     =
+  Tree { treeInfo     =
             newBase repo (Pending (doWriteTree >=> return . snd)) Nothing
-       , _treeContents = M.empty }
+       , treeContents = M.empty }
 
 lookupTree :: Oid -> Repository -> IO (Maybe Tree)
 lookupTree oid repo =
   lookupObject' oid repo c'git_tree_lookup c'git_tree_lookup_prefix $
-    \coid obj _ ->
-      return Tree { _treeInfo =
+    \coid obj _ -> do
+      entriesMap <- withForeignPtr obj $ \treePtr -> do
+        entryCount <- c'git_tree_entrycount (castPtr treePtr)
+        foldM
+          (\m idx -> do
+              entry   <- c'git_tree_entry_byindex (castPtr treePtr)
+                                                 (fromIntegral idx)
+              entryId <- c'git_tree_entry_id entry
+              coid    <- mallocForeignPtr
+              withForeignPtr coid $ \coid' ->
+                c'git_oid_cpy coid' entryId
+
+              entryName  <- c'git_tree_entry_name entry
+                            >>= peekCString >>= return . T.pack
+              entryAttrs <- c'git_tree_entry_attributes entry
+              entryType  <- c'git_tree_entry_type entry
+
+              let entryObj = if entryType == c'GIT_OBJ_BLOB
+                             then BlobEntry (IdRef (COid coid)) False
+                             else TreeEntry (IdRef (COid coid))
+
+              return $ M.insert entryName entryObj m)
+          M.empty [1..entryCount]
+      return Tree { treeInfo =
                        newBase repo (Stored coid) (Just obj)
-                  , _treeContents = M.empty }
+                  , treeContents = entriesMap }
 
 doLookupTreeEntry :: [Text] -> Tree -> IO (Maybe TreeEntry)
-doLookupTreeEntry [] _ = throw TreeEntryLookupFailed
+doLookupTreeEntry [] t = return (Just (TreeEntry (ObjRef t)))
 doLookupTreeEntry (name:names) t = do
   -- Lookup the current name in this tree.  If it doesn't exist, and there are
   -- more names in the path and 'createIfNotExist' is True, create a new Tree
   -- and descend into it.  Otherwise, if it exists we'll have @Just (TreeEntry
   -- {})@, and if not we'll have Nothing.
-  y <- case M.lookup name (t^.treeContents) of
+  Prelude.putStrLn $ "Tree: " ++ show t
+  Prelude.putStrLn $ "Tree Entries: " ++ show (treeContents t)
+  Prelude.putStrLn $ "Lookup: " ++ toString name
+  y <- case M.lookup name (treeContents t) of
     Nothing -> return Nothing
     Just j  -> case j of
       BlobEntry b mode -> do
@@ -106,13 +133,15 @@
         tr <- loadObject t' t
         for tr $ \x -> return $ TreeEntry (ObjRef x)
 
+  Prelude.putStrLn $ "Result: " ++ show y
+  Prelude.putStrLn $ "Names: " ++ show names
   if null names
     then return y
     else
       case y of
         Just (BlobEntry {}) -> throw TreeCannotTraverseBlob
         Just (TreeEntry (ObjRef t')) -> doLookupTreeEntry names t'
-        _ -> throw TreeEntryLookupFailed
+        _ -> return Nothing
 
 lookupTreeEntry :: FilePath -> Tree -> IO (Maybe TreeEntry)
 lookupTreeEntry = doLookupTreeEntry . splitPath
@@ -124,10 +153,10 @@
     case tref of
       IdRef (COid oid) -> withGitTreeOid repoPtr oid
 
-      ObjRef (Tree { _treeInfo = Base { _gitId = Stored (COid oid) } }) ->
+      ObjRef (Tree { treeInfo = Base { gitId = Stored (COid oid) } }) ->
         withGitTreeOid repoPtr oid
 
-      ObjRef (Tree { _treeInfo = Base { _gitObj = Just t } }) ->
+      ObjRef (Tree { treeInfo = Base { gitObj = Just t } }) ->
         withForeignPtr t (f . castPtr)
 
       ObjRef t -> do t' <- update t
@@ -143,7 +172,7 @@
 -- | Write out a tree to its repository.  If it has already been written,
 --   nothing will happen.
 writeTree :: Tree -> IO Tree
-writeTree t@(Tree { _treeInfo = Base { _gitId = Stored _ } }) = return t
+writeTree t@(Tree { treeInfo = Base { gitId = Stored _ } }) = return t
 writeTree t = fst <$> doWriteTree t
 
 doWriteTree :: Tree -> IO (Tree, COid)
@@ -153,50 +182,48 @@
     when (r < 0) $ throwIO TreeBuilderCreateFailed
     builder <- peek ptr
 
-    newList <-
-      for (M.toList (t^.treeContents)) $ \(k, v) -> do
-        newObj <-
-          case v of
-            BlobEntry bl exe ->
-              flip BlobEntry exe <$>
-                insertObject builder k bl (if exe
-                                           then 0o100755
-                                           else 0o100644)
-            TreeEntry tr ->
-              TreeEntry <$> insertObject builder k tr 0o040000
-        return (k, newObj)
+    -- jww (2012-10-14): With the loose object backend, there should be no
+    -- race conditions here as there will never be a request to access the
+    -- same file by multiple threads.  If that ever does happen, or if this
+    -- code is changed to write to the packed object backend, simply change
+    -- the function 'parallel' to 'sequence' here.
+    oids <- parallel $
+           flip map (M.toList (treeContents t)) $ \(k, v) ->
+             case v of
+               BlobEntry bl exe ->
+                 withObject bl t $ \bl' -> do
+                   (Oid coid) <- objectId bl'
+                   return (k, BlobEntry (IdRef coid) exe, coid,
+                           if exe then 0o100755 else 0o100644)
+               TreeEntry tr ->
+                 withObject tr t $ \tr' -> do
+                   (Oid coid) <- objectId tr'
+                   return (k, TreeEntry (IdRef coid), coid, 0o040000)
 
+    newList <- for oids $ \(k, entry, coid, flags) -> do
+                insertObject builder k coid flags
+                return (k, entry)
+
     coid <- mallocForeignPtr
     withForeignPtr coid $ \coid' -> do
       r3 <- c'git_treebuilder_write coid' repoPtr builder
       when (r3 < 0) $ throwIO TreeBuilderWriteFailed
 
-    return (treeInfo.gitId .~ Stored (COid coid) $
-            treeContents   .~ M.fromList newList $ t, COid coid)
+    return (t { treeInfo     = (treeInfo t) { gitId = Stored (COid coid) }
+              , treeContents = M.fromList newList }, COid coid)
 
   where
-    repo = fromMaybe (error "Repository invalid") $
-                     t^.treeInfo.gitRepo.repoObj
-
-    insertObject :: (CStringable a, Updatable b, Show b)
-                 => Ptr C'git_treebuilder -> a -> ObjRef b -> CUInt
-                 -> IO (ObjRef b)
-    insertObject builder key obj attrs = do
-      coid <- case obj of
-        IdRef (COid x) -> return x
-        ObjRef x -> do
-          oid <- objectId x
-          case oid of
-            Oid (COid y) -> return y
-            _ -> error $ "Failed to determine object id for: " ++ show x
+    repo = fromMaybe (error "Repository invalid")
+                     (repoObj (gitRepo (treeInfo t)))
 
+    insertObject :: (CStringable a)
+                 => Ptr C'git_treebuilder -> a -> COid -> CUInt -> IO ()
+    insertObject builder key (COid coid) attrs =
       withForeignPtr coid $ \coid' ->
         withCStringable key $ \name -> do
           r2 <- c'git_treebuilder_insert nullPtr builder name coid' attrs
           when (r2 < 0) $ throwIO TreeBuilderInsertFailed
 
-      return (IdRef (COid coid))
-
 doModifyTree
   :: [Text] -> (Maybe TreeEntry -> Either a (Maybe TreeEntry)) -> Bool
   -> Tree -> IO (Either a Tree)
@@ -206,12 +233,12 @@
   -- more names in the path and 'createIfNotExist' is True, create a new Tree
   -- and descend into it.  Otherwise, if it exists we'll have @Just (TreeEntry
   -- {})@, and if not we'll have Nothing.
-  y <- case M.lookup name (t^.treeContents) of
+  y <- case M.lookup name (treeContents t) of
     Nothing ->
       return $
         if createIfNotExist && not (null names)
         then Just . TreeEntry . ObjRef . createTree
-             $ t^.treeInfo.gitRepo
+             $ gitRepo (treeInfo t)
         else Nothing
     Just j -> case j of
       BlobEntry b mode -> do
@@ -238,11 +265,11 @@
       Left err -> return $ Left err
       Right z ->
         return $ Right $
-          treeInfo     .~ newTreeBase t $
-          treeContents .~
-            (case z of
-               Nothing -> M.delete name (t^.treeContents)
-               Just z' -> M.insert name z' (t^.treeContents)) $ t
+          t { treeInfo     = newTreeBase t
+            , treeContents =
+              case z of
+                Nothing -> M.delete name (treeContents t)
+                Just z' -> M.insert name z' (treeContents t) }
 
     else
       -- If there are further names in the path, descend them now.  If
@@ -258,12 +285,12 @@
             err@(Left _) -> return err
             Right st' ->
               return $ Right $
-                treeInfo     .~ newTreeBase t $
-                treeContents .~
-                  (if M.null (st'^.treeContents)
-                   then M.delete name (t^.treeContents)
-                   else M.insert name (TreeEntry (ObjRef st'))
-                                 (t^.treeContents)) $ t
+                t { treeInfo     = newTreeBase t
+                  , treeContents =
+                    if M.null (treeContents st')
+                    then M.delete name (treeContents t)
+                    else M.insert name (TreeEntry (ObjRef st'))
+                                  (treeContents t) }
         _ -> throw TreeLookupFailed
 
 modifyTree
diff --git a/gitlib.cabal b/gitlib.cabal
--- a/gitlib.cabal
+++ b/gitlib.cabal
@@ -1,5 +1,5 @@
 Name:                gitlib
-Version:             0.4.1
+Version:             0.5.0
 Synopsis:            Higher-level types for working with hlibgit2
 Description:         Higher-level types for working with hlibgit2
 License-file:        LICENSE
@@ -15,45 +15,54 @@
   location: git://github.com/jwiegley/gitlib.git
 
 Test-suite smoke
-  default-language: Haskell98
-  type: exitcode-stdio-1.0
-  main-is: Main.hs
-  hs-source-dirs: test
-  build-depends:
-      base >=3
+  Default-language: Haskell98
+  Type: exitcode-stdio-1.0
+  Main-is: Smoke.hs
+  Hs-source-dirs: test
+  Ghc-options: -Wall -Werror
+  Build-depends: base >=3
     , gitlib
     , HUnit           >= 1.2.5
     , bytestring      >= 0.9.2.1
     , containers      >= 0.4.2
     , lens            >= 2.8
+    , parallel-io     >= 0.3.2.1
     , stringable      >= 0.1.1
     , system-fileio   >= 0.3.9
     , system-filepath >= 0.4.7
     , text            >= 0.11.2
     , time            >= 1.4
 
-test-suite doctests
-  default-language: Haskell98
-  type:    exitcode-stdio-1.0
-  main-is: doctests.hs
-  build-depends:
-      base == 4.*
+Test-suite doctests
+  Default-language: Haskell98
+  Type:    exitcode-stdio-1.0
+  Main-is: Doctest.hs
+  Hs-source-dirs: test
+  Ghc-options: -Wall -Werror
+  Build-depends: base == 4.*
     , directory >= 1.0 && < 1.3
     , doctest   >= 0.8 && <= 0.10
     , filepath  >= 1.3 && < 1.4
-  ghc-options: -Wall -Werror
-  hs-source-dirs: test
 
+Test-suite unittests
+  Default-language:  Haskell2010
+  Type:              exitcode-stdio-1.0
+  Main-is:           Unit.hs
+  Hs-source-dirs: test
+  Build-depends: base == 4.*
+    , HTF == 0.10.*
+
 Library
   default-language: Haskell98
   default-extensions:
     ForeignFunctionInterface
   build-depends:
       base            >= 3 && < 5
-    , hlibgit2        >= 0.6
+    , hlibgit2        >= 0.17
     , bytestring      >= 0.9.2.1
     , containers      >= 0.4.2
     , lens            >= 2.8
+    , parallel-io     >= 0.3.2.1
     , stringable      >= 0.1.1
     , system-fileio   >= 0.3.9
     , system-filepath >= 0.4.7
diff --git a/test/Doctest.hs b/test/Doctest.hs
new file mode 100644
--- /dev/null
+++ b/test/Doctest.hs
@@ -0,0 +1,29 @@
+module Main where
+
+import Test.DocTest
+import System.Directory
+import System.FilePath
+import Control.Applicative
+import Control.Monad
+import Data.List
+
+main :: IO ()
+main = getSources >>= \sources -> doctest $
+    "-isrc"
+  : "-idist/build/autogen"
+  : "-optP-include"
+  : "-optPdist/build/autogen/cabal_macros.h"
+  : sources
+
+getSources :: IO [FilePath]
+getSources = filter (isSuffixOf ".hs") <$> go "Data/Git"
+  where
+    go dir = do
+      (dirs, files) <- getFilesAndDirectories dir
+      (files ++) . concat <$> mapM go dirs
+
+getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])
+getFilesAndDirectories dir = do
+  c <- map (dir </>) . filter (`notElem` ["..", "."])
+           <$> getDirectoryContents dir
+  (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
diff --git a/test/Main.hs b/test/Main.hs
deleted file mode 100644
--- a/test/Main.hs
+++ /dev/null
@@ -1,209 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
-{-# OPTIONS_GHC -fno-warn-wrong-do-bind #-}
-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
-
-module Main where
-
-import           Control.Applicative
-import           Control.Lens
-import           Control.Monad
-import           Data.Git
-import           Data.Maybe
-import           Data.Text as T hiding (map)
-import qualified Data.Text.Encoding as E
-import           Data.Time.Clock.POSIX
-import           Data.Traversable
-import           Filesystem (removeTree, isDirectory)
-import           Filesystem.Path.CurrentOS
-import qualified Prelude
-import           Prelude hiding (FilePath, putStr, putStrLn)
-import           System.Exit
-import           Test.HUnit
-
-default (Text)
-
-main :: IO ()
-main = do
-  counts' <- runTestTT tests
-  case counts' of
-    Counts _ _ errors' failures' ->
-      if errors' > 0 || failures' > 0
-      then exitFailure
-      else exitSuccess
-
-catBlob :: Repository -> Text -> IO (Maybe Text)
-catBlob repo sha = do
-  hash <- stringToOid sha
-  for hash $ \hash' -> do
-    obj <- lookupObject hash' repo
-    case obj of
-      Just (BlobObj b) -> do
-        (_, contents) <- getBlobContents b
-        return (E.decodeUtf8 contents)
-
-      Just _  -> error "Found something else..."
-      Nothing -> error "Didn't find anything :("
-
-withRepository :: Text -> (Repository -> Assertion) -> Assertion
-withRepository n f = do
-  let p = fromText n
-  exists <- isDirectory p
-  when exists $ removeTree p
-
-  -- we want exceptions to leave the repo behind
-  f =<< createRepository p True
-
-  removeTree p
-
-oid :: Updatable a => a -> IO Text
-oid = objectId >=> return . oidToText
-
-oidToText :: Oid -> Text
-oidToText = T.pack . show
-
-tests :: Test
-tests = test [
-
-  "singleBlob" ~:
-
-  withRepository "singleBlob.git" $ \repo -> do
-    update_ $ createBlob (E.encodeUtf8 "Hello, world!\n") repo
-
-    x <- catBlob repo "af5626b4a114abcb82d63db7c8082c3c4756e51b"
-    (@?=) x (Just "Hello, world!\n")
-
-    x <- catBlob repo "af5626b"
-    (@?=) x (Just "Hello, world!\n")
-
-    return ()
-
-  , "singleTree" ~:
-
-  withRepository "singleTree.git" $ \repo -> do
-    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
-    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
-    x  <- oid tr
-    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
-
-    return()
-
-  , "twoTrees" ~:
-
-  withRepository "twoTrees.git" $ \repo -> do
-    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
-    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
-    x  <- oid tr
-    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
-
-    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
-    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
-    x  <- oid tr
-    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
-
-    return()
-
-  , "deleteTree" ~:
-
-  withRepository "deleteTree.git" $ \repo -> do
-    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
-    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
-    x  <- oid tr
-    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
-
-    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
-    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
-    x  <- oid tr
-    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
-
-    -- Confirm that deleting world.txt also deletes the now-empty subtree
-    -- goodbye/files, which also deletes the then-empty subtree goodbye,
-    -- returning us back the original tree.
-    tr <- removeFromTree "goodbye/files/world.txt" tr
-    x  <- oid tr
-    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
-
-    return()
-
-  , "createCommit" ~:
-
-  withRepository "createCommit.git" $ \repo -> do
-    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
-    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
-
-    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
-    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
-    x  <- oid tr
-    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
-
-    -- The Oid has been cleared in tr, so this tests that it gets written as
-    -- needed.
-    let sig  = Signature {
-            _signatureName  = "John Wiegley"
-          , _signatureEmail = "johnw@newartisans.com"
-          , _signatureWhen  = posixSecondsToUTCTime 1348980883 }
-
-    x <- oid $ commitTree      .~ ObjRef tr
-            $ commitAuthor    .~ sig
-            $ commitCommitter .~ sig
-            $ commitLog       .~ "Sample log message."
-            $ createCommit repo
-    (@?=) x "44381a5e564d19893d783a5d5c59f9c745155b56"
-
-    return()
-
-  , "createTwoCommits" ~:
-
-  withRepository "createTwoCommits.git" $ \repo -> do
-    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
-    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
-
-    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
-    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
-    x  <- oid tr
-    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
-
-    -- The Oid has been cleared in tr, so this tests that it gets written as
-    -- needed.
-    let sig = Signature {
-            _signatureName  = "John Wiegley"
-          , _signatureEmail = "johnw@newartisans.com"
-          , _signatureWhen  = posixSecondsToUTCTime 1348980883 }
-        c   = commitTree      .~ ObjRef tr
-            $ commitAuthor    .~ sig
-            $ commitCommitter .~ sig
-            $ commitLog       .~ "Sample log message."
-            $ createCommit repo
-    x <- oid c
-    (@?=) x "44381a5e564d19893d783a5d5c59f9c745155b56"
-
-    let goodbye2 = createBlob (E.encodeUtf8 "Goodbye, world again!\n") repo
-    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye2) tr
-    x  <- oid tr
-    (@?=) x "f2b42168651a45a4b7ce98464f09c7ec7c06d706"
-
-    let sig = Signature {
-            _signatureName  = "John Wiegley"
-          , _signatureEmail = "johnw@newartisans.com"
-          , _signatureWhen  = posixSecondsToUTCTime 1348981883 }
-        c2  = commitTree      .~ ObjRef tr
-            $ commitAuthor    .~ sig
-            $ commitCommitter .~ sig
-            $ commitLog       .~ "Second sample log message."
-            $ commitParents   .~ [ObjRef c]
-            $ createCommit repo
-    x <- oid c2
-    (@?=) x "2506e7fcc2dbfe4c083e2bd741871e2e14126603"
-
-    cid <- objectId c2
-    writeRef $ createRef "refs/heads/master" (RefTargetId cid) repo
-    writeRef $ createRef "HEAD" (RefTargetSymbolic "refs/heads/master") repo
-
-    x <- oidToText <$> lookupId "refs/heads/master" repo
-    (@?=) x "2506e7fcc2dbfe4c083e2bd741871e2e14126603"
-
-    return()
-
-  ]
-
--- Main.hs ends here
diff --git a/test/Smoke.hs b/test/Smoke.hs
new file mode 100644
--- /dev/null
+++ b/test/Smoke.hs
@@ -0,0 +1,206 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+{-# OPTIONS_GHC -fno-warn-wrong-do-bind #-}
+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
+
+module Main where
+
+import           Control.Applicative
+import           Control.Concurrent.ParallelIO
+import           Control.Monad
+import           Data.Git
+import           Data.Maybe
+import           Data.Text as T hiding (map)
+import qualified Data.Text.Encoding as E
+import           Data.Time.Clock.POSIX
+import           Data.Traversable
+import           Filesystem (removeTree, isDirectory)
+import           Filesystem.Path.CurrentOS
+import qualified Prelude
+import           Prelude hiding (FilePath, putStr, putStrLn)
+import           System.Exit
+import           Test.HUnit
+
+default (Text)
+
+main :: IO ()
+main = do
+  counts' <- runTestTT tests
+  case counts' of
+    Counts _ _ errors' failures' ->
+      if errors' > 0 || failures' > 0
+      then exitFailure
+      else exitSuccess
+  stopGlobalPool
+
+catBlob :: Repository -> Text -> IO (Maybe Text)
+catBlob repo sha = do
+  hash <- stringToOid sha
+  for hash $ \hash' -> do
+    obj <- lookupObject hash' repo
+    case obj of
+      Just (BlobObj b) -> do
+        (_, contents) <- getBlobContents b
+        return (E.decodeUtf8 contents)
+
+      Just _  -> error "Found something else..."
+      Nothing -> error "Didn't find anything :("
+
+withRepository :: Text -> (Repository -> Assertion) -> Assertion
+withRepository n f = do
+  let p = fromText n
+  exists <- isDirectory p
+  when exists $ removeTree p
+
+  -- we want exceptions to leave the repo behind
+  f =<< createRepository p True
+
+  removeTree p
+
+oid :: Updatable a => a -> IO Text
+oid = objectId >=> return . oidToText
+
+oidToText :: Oid -> Text
+oidToText = T.pack . show
+
+sampleCommit :: Repository -> Tree -> Signature -> Commit
+sampleCommit repo tr sig =
+    (createCommit repo) { commitTree      = ObjRef tr
+                        , commitAuthor    = sig
+                        , commitCommitter = sig
+                        , commitLog       = "Sample log message." }
+
+tests :: Test
+tests = test [
+
+  "singleBlob" ~:
+
+  withRepository "singleBlob.git" $ \repo -> do
+    update_ $ createBlob (E.encodeUtf8 "Hello, world!\n") repo
+
+    x <- catBlob repo "af5626b4a114abcb82d63db7c8082c3c4756e51b"
+    (@?=) x (Just "Hello, world!\n")
+
+    x <- catBlob repo "af5626b"
+    (@?=) x (Just "Hello, world!\n")
+
+    return ()
+
+  , "singleTree" ~:
+
+  withRepository "singleTree.git" $ \repo -> do
+    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
+    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
+    x  <- oid tr
+    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
+
+    return()
+
+  , "twoTrees" ~:
+
+  withRepository "twoTrees.git" $ \repo -> do
+    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
+    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
+    x  <- oid tr
+    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
+
+    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
+    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
+    x  <- oid tr
+    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
+
+    return()
+
+  , "deleteTree" ~:
+
+  withRepository "deleteTree.git" $ \repo -> do
+    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
+    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
+    x  <- oid tr
+    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
+
+    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
+    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
+    x  <- oid tr
+    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
+
+    -- Confirm that deleting world.txt also deletes the now-empty subtree
+    -- goodbye/files, which also deletes the then-empty subtree goodbye,
+    -- returning us back the original tree.
+    tr <- removeFromTree "goodbye/files/world.txt" tr
+    x  <- oid tr
+    (@?=) x "c0c848a2737a6a8533a18e6bd4d04266225e0271"
+
+    return()
+
+  , "createCommit" ~:
+
+  withRepository "createCommit.git" $ \repo -> do
+    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
+    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
+
+    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
+    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
+    x  <- oid tr
+    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
+
+    -- The Oid has been cleared in tr, so this tests that it gets written as
+    -- needed.
+    let sig  = Signature {
+            signatureName  = "John Wiegley"
+          , signatureEmail = "johnw@newartisans.com"
+          , signatureWhen  = posixSecondsToUTCTime 1348980883 }
+
+    x <- oid $ sampleCommit repo tr sig
+    (@?=) x "44381a5e564d19893d783a5d5c59f9c745155b56"
+
+    return()
+
+  , "createTwoCommits" ~:
+
+  withRepository "createTwoCommits.git" $ \repo -> do
+    let hello = createBlob (E.encodeUtf8 "Hello, world!\n") repo
+    tr <- updateTree "hello/world.txt" (blobRef hello) (createTree repo)
+
+    let goodbye = createBlob (E.encodeUtf8 "Goodbye, world!\n") repo
+    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye) tr
+    x  <- oid tr
+    (@?=) x "98c3f387f63c08e1ea1019121d623366ff04de7a"
+
+    -- The Oid has been cleared in tr, so this tests that it gets written as
+    -- needed.
+    let sig = Signature {
+            signatureName  = "John Wiegley"
+          , signatureEmail = "johnw@newartisans.com"
+          , signatureWhen  = posixSecondsToUTCTime 1348980883 }
+        c   = sampleCommit repo tr sig
+    x <- oid c
+    (@?=) x "44381a5e564d19893d783a5d5c59f9c745155b56"
+
+    let goodbye2 = createBlob (E.encodeUtf8 "Goodbye, world again!\n") repo
+    tr <- updateTree "goodbye/files/world.txt" (blobRef goodbye2) tr
+    x  <- oid tr
+    (@?=) x "f2b42168651a45a4b7ce98464f09c7ec7c06d706"
+
+    let sig = Signature {
+            signatureName  = "John Wiegley"
+          , signatureEmail = "johnw@newartisans.com"
+          , signatureWhen  = posixSecondsToUTCTime 1348981883 }
+        c2  = (sampleCommit repo tr sig) {
+                  commitLog       = "Second sample log message."
+                , commitParents   = [ObjRef c] }
+    x <- oid c2
+    (@?=) x "2506e7fcc2dbfe4c083e2bd741871e2e14126603"
+
+    cid <- objectId c2
+    writeRef $ createRef "refs/heads/master" (RefTargetId cid) repo
+    writeRef $ createRef "HEAD" (RefTargetSymbolic "refs/heads/master") repo
+
+    x <- oidToText <$> lookupId "refs/heads/master" repo
+    (@?=) x "2506e7fcc2dbfe4c083e2bd741871e2e14126603"
+
+    return()
+
+  ]
+
+-- Main.hs ends here
diff --git a/test/Unit.hs b/test/Unit.hs
new file mode 100644
--- /dev/null
+++ b/test/Unit.hs
@@ -0,0 +1,9 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+
+import System.Environment ( getArgs )
+import System.Exit ( exitWith )
+import Test.Framework
+
+test_equality = assertEqual "foo" "foo"
+
+main = htfMain htf_thisModulesTests
diff --git a/test/doctests.hs b/test/doctests.hs
deleted file mode 100644
--- a/test/doctests.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module Main where
-
-import Test.DocTest
-import System.Directory
-import System.FilePath
-import Control.Applicative
-import Control.Monad
-import Data.List
-
-main :: IO ()
-main = getSources >>= \sources -> doctest $
-    "-isrc"
-  : "-idist/build/autogen"
-  : "-optP-include"
-  : "-optPdist/build/autogen/cabal_macros.h"
-  : sources
-
-getSources :: IO [FilePath]
-getSources = filter (isSuffixOf ".hs") <$> go "Data/Git"
-  where
-    go dir = do
-      (dirs, files) <- getFilesAndDirectories dir
-      (files ++) . concat <$> mapM go dirs
-
-getFilesAndDirectories :: FilePath -> IO ([FilePath], [FilePath])
-getFilesAndDirectories dir = do
-  c <- map (dir </>) . filter (`notElem` ["..", "."])
-           <$> getDirectoryContents dir
-  (,) <$> filterM doesDirectoryExist c <*> filterM doesFileExist c
