diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# gitson [![Hackage](https://img.shields.io/hackage/v/gitson.svg?style=flat)](http://hackage.haskell.org/package/gitson) [![Build Status](https://img.shields.io/travis/myfreeweb/gitson.svg?style=flat)](https://travis-ci.org/myfreeweb/gitson) [![Coverage Status](https://img.shields.io/coveralls/myfreeweb/gitson.svg?style=flat)](https://coveralls.io/r/myfreeweb/gitson) [![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg?style=flat)](https://www.tldrlegal.com/l/apache2)
+# gitson [![Hackage](https://img.shields.io/hackage/v/gitson.svg?style=flat)](https://hackage.haskell.org/package/gitson) [![Build Status](https://img.shields.io/travis/myfreeweb/gitson.svg?style=flat)](https://travis-ci.org/myfreeweb/gitson) [![Coverage Status](https://img.shields.io/coveralls/myfreeweb/gitson.svg?style=flat)](https://coveralls.io/r/myfreeweb/gitson) [![Apache License 2.0](https://img.shields.io/badge/license-Apache%202.0-brightgreen.svg?style=flat)](https://www.tldrlegal.com/l/apache2)
 
 A simple document store library for Git + JSON, based on [Aeson].
 Uses command line git, at least for now.
@@ -11,13 +11,15 @@
 ## Usage
 
 ```haskell
+{-# LANGUAGE TemplateHaskell #-}
+
 import Gitson
 import Gitson.Util (insideDirectory)
 import Data.Aeson.TH
 import Control.Monad.IO.Class (liftIO)
 
 data Thing = Thing { val :: Int } deriving (Eq, Show)
-$(deriveJSON defaultOptions ''Thing)
+$(deriveJSON defaultOptions ''Thing) -- there are non-Template ways of doing this, see aeson docs
 
 main :: IO ()
 main = do
@@ -25,8 +27,9 @@
   createRepo "./content"
 
   -- Writing data to a "database" happens in transactions
-  -- A transaction is committed after the block is executed
-  -- Just like in SQL databases
+  -- A transaction is committed (write files & git commit)
+  -- after the block is executed, just like in SQL databases
+  -- Also, transactions are thread-safe
   transaction "./content" $ do
     -- order: (collection) (key        ) (data)
     saveEntry "content"    "first-thing" Thing {val = 1}
@@ -37,18 +40,22 @@
 
   -- Reading data
   -- (These are normal IO actions, so if you want
-  --  to read inside of a transaction, liftIO)
-  keys <- listEntries "./content/things"
-       -- Just ["first-thing"]
-  first-thing <- readEntry "./content/things" "first-thing" :: IO (Maybe Thing)
-              -- Just Thing {val = 1}
-
-  -- Reading data, avoiding repetition of the repo path
+  --  to read inside of a transaction, liftIO.
+  --  Note: transaction already includes insideDirectory!)
   insideDirectory "./content" $ do
-    keys <- listEntries "things"
-         -- Just ["first-thing"]
+    colls <- listCollections
+          -- ["things"]
+    keys <- listEntryKeys "things"
+         -- ["first-thing"]
     first-thing <- readEntry "things" "first-thing" :: IO (Maybe Thing)
          -- Just Thing {val = 1}
+    things <- readEntries "things" :: IO [Thing]
+           -- [Thing {val = 1}]
+
+  -- Note: insideDirectory is just a function that changes
+  -- the current directory, executes an action and changes it back.
+  -- You can use reading actions without it, like this:
+  keys <- listEntryKeys "./content/things"
 ```
 
 ## Development
diff --git a/gitson.cabal b/gitson.cabal
--- a/gitson.cabal
+++ b/gitson.cabal
@@ -1,5 +1,5 @@
 name:            gitson
-version:         0.1.0
+version:         0.2.0
 synopsis:        A document store library for Git + JSON.
 description:     A simple document store library for Git + JSON, based on Aeson. Uses command line git, at least for now. No fancy indexes and stuff, but it does what I need right now. Transactions use flock, so it's safe even across completely separate programs!
 category:        Database
@@ -54,7 +54,7 @@
     build-depends:
         base >= 4.0.0.0 && < 5
       , Glob == 0.*
-      , doctest == 0.*
+      , doctest >= 0.8
     default-language: Haskell2010
     ghc-options: -threaded -Wall -Werror
     hs-source-dirs: test-suite
@@ -73,7 +73,7 @@
         , HUnit
         , QuickCheck
     default-language: Haskell2010
-    ghc-options: -threaded -Wall -Werror
+    ghc-options: -threaded -Wall -Werror -fhpc
     hs-source-dirs: test-suite
     main-is: Spec.hs
     other-modules:
diff --git a/library/Gitson.hs b/library/Gitson.hs
--- a/library/Gitson.hs
+++ b/library/Gitson.hs
@@ -1,9 +1,7 @@
 -- | Gitson is a simple document store library for Git + JSON.
 module Gitson (module Gitson) where
 
-import           Prelude hiding (catch)
 import           System.Directory
-import           System.FilePath
 import           System.Lock.FLock
 import           Control.Exception
 import           Control.Applicative
@@ -11,6 +9,7 @@
 import           Control.Monad.Trans.Writer
 import           Control.Monad.IO.Class (liftIO)
 import           Data.Aeson (ToJSON, encode, FromJSON, decode)
+import           Data.Maybe (fromMaybe)
 import qualified Data.ByteString.Lazy as BL
 import           Gitson.Util
 
@@ -48,7 +47,20 @@
   return $ decode =<< hush jsonString
 
 -- | Lists entry keys in a collection.
-listEntries :: FilePath -> IO (Maybe [FilePath])
-listEntries collection = do
+listEntryKeys :: FilePath -> IO [FilePath]
+listEntryKeys collection = do
   contents <- try (getDirectoryContents collection) :: IO (Either IOException [FilePath])
-  return $ filterFilenamesAsKeys <$> hush contents
+  return $ filterFilenamesAsKeys $ fromMaybe [] $ hush contents
+
+-- | Lists entries in a collection.
+listEntries :: FromJSON a => FilePath -> IO [a]
+listEntries collection = do
+  ks <- listEntryKeys collection
+  maybes <- mapM (readEntry collection) ks
+  return $ fromMaybe [] $ sequence maybes
+
+-- | Lists collections in the current repository.
+listCollections :: IO [FilePath]
+listCollections = do
+  contents <- try (getDirectoryContents =<< getCurrentDirectory) :: IO (Either IOException [FilePath])
+  filterDirs $ fromMaybe [] $ hush contents
diff --git a/library/Gitson/Util.hs b/library/Gitson/Util.hs
--- a/library/Gitson/Util.hs
+++ b/library/Gitson/Util.hs
@@ -1,9 +1,8 @@
 -- | Various functions used inside Gitson.
 module Gitson.Util (module Gitson.Util) where
 
-import           Data.Char (isSpace)
-import           Control.Monad (void)
-import           Control.Applicative
+import           Control.Monad (void, filterM)
+import           Data.List (isSuffixOf, isPrefixOf)
 import           System.FilePath
 import           System.Directory
 import           System.Process
@@ -23,13 +22,17 @@
 lockPath :: FilePath
 lockPath = ".git" </> "gitson-lock"
 
--- | Turns a list of filenames into a list of keys.
+-- | Turns a list of filenames into a list of keys, ignoring non-JSON files.
 --
--- >>> filterFilenamesAsKeys [".", "..", "k1.json"]
+-- >>> filterFilenamesAsKeys [".", "..", "k1.json", "unrelated.file"]
 -- ["k1"]
 filterFilenamesAsKeys :: [String] -> [String]
-filterFilenamesAsKeys = map dropExtension . filter (`notElem` [".", ".."])
+filterFilenamesAsKeys = map dropExtension . filter (isSuffixOf ".json")
 
+-- | Filters a list of file paths, leaving only paths to existing non-hidden directories.
+filterDirs :: [FilePath] -> IO [FilePath]
+filterDirs = (filterM doesDirectoryExist) . filter (not . isPrefixOf ".")
+
 -- | Returns an IO action that switches the current directory to a given path,
 -- executes the given IO action and switches the current directory back.
 insideDirectory :: FilePath -> IO a -> IO a
@@ -40,17 +43,6 @@
   setCurrentDirectory prevPath
   return result
 
--- | Removes trailing whitespace like the newline you get from executing commands.
---
--- >>> stripWhitespaceRight "/path/to/thingy \n\n\n"
--- "/path/to/thingy"
-stripWhitespaceRight :: FilePath -> FilePath
-stripWhitespaceRight = reverse . dropWhile isSpace . reverse
-
--- | Finds the path to the git repository a given path belongs to.
-findRepoRoot :: FilePath -> IO FilePath
-findRepoRoot path = stripWhitespaceRight <$> (insideDirectory path $ readProcess "git" ["rev-parse", "--show-toplevel"] [])
-
 -- | Returns the message of the last git commit in the repo where the current directory is located.
 lastCommitText :: IO String
 lastCommitText = readProcess "git" ["log", "--max-count=1", "--pretty=format:%s"] []
@@ -62,6 +54,6 @@
 -- | Runs a shell command with stdin, stdout and stderr set to /dev/null.
 shell :: String -> [String] -> IO ()
 shell cmd args = void $ do
-  null <- devNull
-  (_, _, _, pid) <- createProcess (proc cmd args){std_in = UseHandle null, std_out = UseHandle null, std_err = UseHandle null}
+  dnull <- devNull
+  (_, _, _, pid) <- createProcess (proc cmd args){std_in = UseHandle dnull, std_out = UseHandle dnull, std_err = UseHandle dnull}
   waitForProcess pid
diff --git a/test-suite/DocTest.hs b/test-suite/DocTest.hs
--- a/test-suite/DocTest.hs
+++ b/test-suite/DocTest.hs
@@ -1,7 +1,7 @@
 module Main (main) where
 
 import           System.FilePath.Glob (glob)
-import           Test.DocTest         (doctest)
+import           Test.DocTest
 
 main :: IO ()
 main = glob "library/**/*.hs" >>= doctest
diff --git a/test-suite/GitsonSpec.hs b/test-suite/GitsonSpec.hs
--- a/test-suite/GitsonSpec.hs
+++ b/test-suite/GitsonSpec.hs
@@ -6,14 +6,13 @@
 import           System.Directory
 import           Data.Aeson.TH
 import           Data.List (sort)
-import           Control.Applicative
 import           Control.Exception
 import           Control.Monad.IO.Class
 import           Control.Monad (void)
 import           Gitson
 import           Gitson.Util (insideDirectory, lastCommitText)
 
-data Thing = Thing { val :: Int } deriving (Eq, Show)
+data Thing = Thing { val :: Int } deriving (Eq, Show, Ord)
 $(deriveJSON defaultOptions ''Thing)
 
 spec :: Spec
@@ -44,17 +43,43 @@
       content <- readEntry "tmp/repo/things" "totally-not-a-thing" :: IO (Maybe Thing)
       content `shouldBe` Nothing
 
-  describe "listEntries" $ do
-    it "returns Just a list of entries when listing a collection" $ do
+  describe "listEntryKeys" $ do
+    it "returns a list of entry keys when listing a collection" $ do
       createDirectoryIfMissing True "tmp/repo/things"
       _ <- writeFile "tmp/repo/things/first-thing.json" "{}"
       _ <- writeFile "tmp/repo/things/second-thing.json" "{}"
-      list <- listEntries "tmp/repo/things"
-      sort <$> list `shouldBe` Just ["first-thing", "second-thing"]
+      list <- listEntryKeys "tmp/repo/things"
+      sort list `shouldBe` ["first-thing", "second-thing"]
 
-    it "returns Nothing when listing a nonexistent collection" $ do
-      list <- listEntries "nonsense"
-      list `shouldBe` Nothing
+    it "returns an empty when listing a nonexistent collection" $ do
+      list <- listEntryKeys "nonsense"
+      list `shouldBe` []
+
+  describe "listEntries" $ do
+    it "returns a list of entries when listing a collection" $ do
+      createDirectoryIfMissing True "tmp/repo/things"
+      _ <- writeFile "tmp/repo/things/first-thing.json" "{\"val\":1}"
+      _ <- writeFile "tmp/repo/things/second-thing.json" "{\"val\":2}"
+      list <- listEntries "tmp/repo/things" :: IO [Thing]
+      sort list `shouldBe` [Thing {val = 1}, Thing {val = 2}]
+
+    it "returns an empty list when listing a nonexistent collection" $ do
+      list <- listEntries "nonsense" :: IO [Thing]
+      list `shouldBe` []
+
+  describe "listCollections" $ do
+    it "returns a list of collections" $ do
+      createDirectoryIfMissing True "tmp/repo/bits"
+      createDirectoryIfMissing True "tmp/repo/pieces"
+      insideDirectory "tmp/repo" $ do
+        list <- listCollections
+        sort list `shouldBe` ["bits", "pieces"]
+
+    it "returns an empty list when listing an empty repo" $ do
+      createDirectoryIfMissing True "tmp/repo"
+      insideDirectory "tmp/repo" $ do
+        list <- listCollections
+        sort list `shouldBe` []
 
 setup :: IO ()
 setup = createRepo "tmp/repo"
