diff --git a/Data/Git.hs b/Data/Git.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git.hs
@@ -0,0 +1,86 @@
+-- |
+-- Module      : Data.Git
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git
+    (
+    -- * Basic types
+      Ref
+    , RefName(..)
+    , Commit(..)
+    , Person(..)
+    , CommitExtra(..)
+    , Tree(..)
+    , Blob(..)
+    , Tag(..)
+    , GitTime
+    , ModePerm(..)
+    , EntName
+    , EntPath
+    , entName
+    , entPathAppend
+
+    -- * Helper & type related to ModePerm
+    , ObjectFileType(..)
+    , FilePermissions(..)
+    , getPermission
+    , getFiletype
+
+    -- * Revision
+    , Revision
+    , resolveRevision
+
+    -- * Object resolution
+    , resolveTreeish
+    , resolvePath
+
+    -- * repo context
+    , Git
+    , withCurrentRepo
+    , withRepo
+    , findRepo
+
+    -- * Repository queries and creation
+    , initRepo
+    , isRepo
+
+    -- * Context operations
+    , rewrite
+
+    -- * Get objects
+    , getObject
+    , getCommit
+    , getTree
+
+    -- * Set objects
+    , setObject
+    , toObject
+
+    -- * Work trees
+    , WorkTree
+    , EntType(..)
+    , workTreeNew
+    , workTreeFrom
+    , workTreeDelete
+    , workTreeSet
+    , workTreeFlush
+
+    -- * Named refs
+    , branchWrite
+    , branchList
+    , tagWrite
+    , tagList
+    , headSet
+    , headGet
+    ) where
+
+import Data.Git.Ref
+import Data.Git.Types
+import Data.Git.Storage
+import Data.Git.Repository
+import Data.Git.Revision
+import Data.Git.Storage.Object (toObject)
+import Data.Git.WorkTree
diff --git a/Data/Git/Config.hs b/Data/Git/Config.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Config.hs
@@ -0,0 +1,84 @@
+-- |
+-- Module      : Data.Git.Config
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- config related types and methods.
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Data.Git.Config
+    ( Config(..)
+    , Section(..)
+    -- * reading methods
+    , readConfig
+    , readGlobalConfig
+    -- * methods
+    , listSections
+    , get
+    ) where
+
+import Data.Git.Path
+import Data.Git.Imports
+import Data.Git.OS
+import Data.List (find)
+import qualified Data.Set as S
+
+newtype Config = Config [Section]
+    deriving (Show,Eq)
+
+data Section = Section
+    { sectionName :: String
+    , sectionKVs  :: [(String, String)]
+    } deriving (Show,Eq)
+
+parseConfig :: String -> Config
+parseConfig = Config . reverse . toSections . foldl accSections ([], Nothing) . lines
+  where toSections (l,Nothing) = l
+        toSections (l,Just s)  = s : l
+
+        -- a new section in the config file
+        accSections (sections, mcurrent) ('[':sectNameE)
+            | last sectNameE == ']' =
+                let sectName = take (length sectNameE - 1) sectNameE
+                 in case mcurrent of
+                    Nothing      -> (sections, Just $ Section sectName [])
+                    Just current -> (sectionFinalize current : sections, Just $ Section sectName [])
+            | otherwise             =
+                (sections, mcurrent)
+        -- a normal line without having any section defined yet
+        accSections acc@(_, Nothing) _ = acc
+        -- potentially a k-v line in an existing section
+        accSections (sections, Just current) kvLine =
+            case break (== '=') kvLine of
+                (k,'=':v) -> (sections, Just $ sectionAppend current (strip k, strip v))
+                (_,_)     -> (sections, Just current) -- not a k = v line
+        -- append a key-value
+        sectionAppend (Section n l) kv = Section n (kv:l)
+        sectionFinalize (Section n l) = Section n $ reverse l
+
+        strip s = dropSpaces $ reverse $ dropSpaces $ reverse s
+          where dropSpaces = dropWhile (\c -> c == ' ' || c == '\t')
+
+readConfigPath :: LocalPath -> IO Config
+readConfigPath filepath = parseConfig <$> readTextFile filepath
+
+readConfig :: LocalPath -> IO Config
+readConfig gitRepo = readConfigPath (configPath gitRepo)
+
+readGlobalConfig :: IO Config
+readGlobalConfig = getHomeDirectory >>= readConfigPath . (\homeDir -> homeDir </> ".gitconfig")
+
+listSections :: [Config] -> [String]
+listSections = S.toList . foldr accSections S.empty
+  where accSections (Config sections) set = foldr S.insert set (map sectionName sections)
+
+-- | Get a configuration element in a stack of config file, starting from the top.
+get :: [Config] -- ^ stack of config
+    -> String   -- ^ section name
+    -> String   -- ^ key name
+    -> Maybe String
+get []            _       _   = Nothing
+get (Config c:cs) section key = findOne `mplus` get cs section key
+  where findOne = find (\s -> sectionName s == section) c >>= lookup key . sectionKVs
diff --git a/Data/Git/Delta.hs b/Data/Git/Delta.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Delta.hs
@@ -0,0 +1,80 @@
+-- |
+-- Module      : Data.Git.Delta
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Delta
+    ( Delta(..)
+    , DeltaCmd(..)
+    , deltaParse
+    , deltaRead
+    , deltaApply
+    ) where
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString.Lazy as L
+import Data.Bits
+import Data.Word
+
+import qualified Data.Git.Parser as P
+import           Data.Git.Imports
+
+-- | a delta is a source size, a destination size and a list of delta cmd
+data Delta = Delta Word64 Word64 [DeltaCmd]
+    deriving (Show,Eq)
+
+-- | possible commands in a delta
+data DeltaCmd =
+      DeltaCopy ByteString   -- command to insert this bytestring
+    | DeltaSrc Word64 Word64 -- command to copy from source (offset, size)
+    deriving (Show,Eq)
+
+-- | parse a delta.
+-- format is 2 variable sizes, followed by delta cmds. for each cmd:
+-- * if first byte MSB is set, we copy from source.
+-- * otherwise, we copy from delta.
+-- * extensions are not handled.
+deltaParse :: P.Parser Delta
+deltaParse = do
+    srcSize <- getDeltaHdrSize
+    resSize <- getDeltaHdrSize
+    dcmds   <- many (P.anyByte >>= parseWithCmd)
+    return $ Delta srcSize resSize dcmds
+  where
+        getDeltaHdrSize = unbytes 0 <$> P.vlf
+        -- use a foldl ..
+        unbytes _  []     = 0
+        unbytes sh (x:xs) = (fromIntegral x) `shiftL` sh + unbytes (sh+7) xs
+        -- parse one command, either an extension, a copy from src, or a copy from delta.
+        parseWithCmd cmd
+            | cmd == 0        = error "delta extension not supported"
+            | cmd `testBit` 7 = do
+                o1 <- word8cond (cmd `testBit` 0) 0
+                o2 <- word8cond (cmd `testBit` 1) 8
+                o3 <- word8cond (cmd `testBit` 2) 16
+                o4 <- word8cond (cmd `testBit` 3) 24
+                s1 <- word8cond (cmd `testBit` 4) 0
+                s2 <- word8cond (cmd `testBit` 5) 8
+                s3 <- word8cond (cmd `testBit` 6) 16
+                let offset = o1 .|. o2 .|. o3 .|. o4
+                let size   = s1 .|. s2 .|. s3
+                return $ DeltaSrc offset (if size == 0 then 0x10000 else size)
+            | otherwise       = DeltaCopy <$> P.take (fromIntegral cmd)
+        word8cond cond sh =
+            if cond then (flip shiftL sh . fromIntegral) <$> P.anyByte else return 0
+
+-- | read one delta from a lazy bytestring.
+deltaRead :: [ByteString] -> Maybe Delta
+deltaRead = P.maybeParseChunks deltaParse -- . L.toChunks
+
+-- | apply a delta on a lazy bytestring, returning a new bytestring.
+deltaApply :: L.ByteString -> Delta -> L.ByteString
+deltaApply src (Delta srcSize _ deltaCmds)
+    | L.length src /= fromIntegral srcSize = error "source size do not match"
+    | otherwise                            = -- FIXME use a bytestring builder here.
+        L.fromChunks $ concatMap resolve deltaCmds
+      where resolve (DeltaSrc o s) = L.toChunks $ takeAt (fromIntegral s) (fromIntegral o) src
+            resolve (DeltaCopy b)  = [b]
+            takeAt sz at = L.take sz . L.drop at
diff --git a/Data/Git/Diff.hs b/Data/Git/Diff.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Diff.hs
@@ -0,0 +1,284 @@
+-- |
+-- Module      : Data.Git.Diff
+-- License     : BSD-style
+-- Maintainer  : Nicolas DI PRIMA <nicolas@di-prima.fr>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- Basic Git diff methods.
+--
+
+module Data.Git.Diff
+    (
+    -- * Basic features
+      BlobContent(..)
+    , BlobState(..)
+    , BlobStateDiff(..)
+    , getDiffWith
+    -- * Default helpers
+    , GitDiff(..)
+    , GitFileContent(..)
+    , FilteredDiff(..)
+    , GitFileRef(..)
+    , GitFileMode(..)
+    , TextLine(..)
+    , defaultDiff
+    , getDiff
+    ) where
+
+import Data.List (find, filter)
+import Data.Char (ord)
+import Data.Git
+import Data.Git.Repository
+import Data.Git.Storage
+import Data.Git.Storage.Object
+import Data.ByteString.Lazy.Char8 as L
+
+import Data.Algorithm.Patience as AP (Item(..), diff)
+
+-- | represents a blob's content (i.e., the content of a file at a given
+-- reference).
+data BlobContent = FileContent [L.ByteString] -- ^ Text file's lines
+                 | BinaryContent L.ByteString -- ^ Binary content
+    deriving (Show)
+
+-- | This is a blob description at a given state (revision)
+data BlobState = BlobState
+    { bsFilename :: EntPath
+    , bsMode     :: ModePerm
+    , bsRef      :: Ref
+    , bsContent  :: BlobContent
+    }
+    deriving (Show)
+
+-- | Two 'BlobState' are equal if they have the same filename, i.e.,
+--
+-- > ((BlobState x _ _ _) == (BlobState y _ _ _)) = (x == y)
+instance Eq BlobState where
+    (BlobState f1 _ _ _) == (BlobState f2 _ _ _) = f2 == f1
+
+-- | Represents a file state between two revisions
+-- A file (a blob) can be present in the first Tree's revision but not in the
+-- second one, then it has been deleted. If only in the second Tree's revision,
+-- then it has been created. If it is in the both, maybe it has been changed.
+data BlobStateDiff = OnlyOld   BlobState
+                   | OnlyNew   BlobState
+                   | OldAndNew BlobState BlobState
+
+buildListForDiff :: Git -> Ref -> IO [BlobState]
+buildListForDiff git ref = do
+    commit <- getCommit git ref
+    tree   <- resolveTreeish git $ commitTreeish commit
+    case tree of
+        Just t -> do htree <- buildHTree git t
+                     buildTreeList htree []
+        _      -> error "cannot build a tree from this reference"
+    where
+        buildTreeList :: HTree -> EntPath -> IO [BlobState]
+        buildTreeList [] _ = return []
+        buildTreeList ((d,n,TreeFile r):xs) pathPrefix = do
+            content <- catBlobFile r
+            let isABinary = isBinaryFile content
+            listTail <- buildTreeList xs pathPrefix
+            case isABinary of
+                False -> return $ (BlobState (entPathAppend pathPrefix n) d r (FileContent $ L.lines content)) : listTail
+                True  -> return $ (BlobState (entPathAppend pathPrefix n) d r (BinaryContent content)) : listTail
+        buildTreeList ((_,n,TreeDir _ subTree):xs) pathPrefix = do
+            l1 <- buildTreeList xs      pathPrefix
+            l2 <- buildTreeList subTree (entPathAppend pathPrefix n)
+            return $ l1 ++ l2
+
+        catBlobFile :: Ref -> IO L.ByteString
+        catBlobFile blobRef = do
+            mobj <- getObjectRaw git blobRef True
+            case mobj of
+                Nothing  -> error "not a valid object"
+                Just obj -> return $ oiData obj
+        getBinaryStat :: L.ByteString -> Double
+        getBinaryStat bs = L.foldl' (\acc w -> acc + if isBin $ ord w then 1 else 0) 0 bs / (fromIntegral $ L.length bs)
+            where
+                isBin :: Int -> Bool
+                isBin i
+                    | i >= 0 && i <= 8   = True
+                    | i == 12            = True
+                    | i >= 14 && i <= 31 = True
+                    | otherwise          = False
+
+        isBinaryFile :: L.ByteString -> Bool
+        isBinaryFile file = let bs = L.take 512 file
+                            in  getBinaryStat bs > 0.0
+
+-- | generate a diff list between two revisions with a given diff helper.
+--
+-- Useful to extract any kind of information from two different revisions.
+-- For example you can get the number of deleted files:
+--
+-- > getdiffwith f 0 head^ head git
+-- >     where f (OnlyOld _) acc = acc+1
+-- >           f _           acc = acc
+--
+-- Or save the list of new files:
+--
+-- > getdiffwith f [] head^ head git
+-- >     where f (OnlyNew bs) acc = (bsFilename bs):acc
+-- >           f _            acc = acc
+getDiffWith :: (BlobStateDiff -> a -> a) -- ^ diff helper (State -> accumulator -> accumulator)
+            -> a                         -- ^ accumulator
+            -> Ref                       -- ^ commit reference (the original state)
+            -> Ref                       -- ^ commit reference (the new state)
+            -> Git                       -- ^ repository
+            -> IO a
+getDiffWith f acc ref1 ref2 git = do
+    commit1 <- buildListForDiff git ref1
+    commit2 <- buildListForDiff git ref2
+    return $ Prelude.foldr f acc $ doDiffWith commit1 commit2
+    where
+        doDiffWith :: [BlobState] -> [BlobState] -> [BlobStateDiff]
+        doDiffWith []        []        = []
+        doDiffWith [bs1]     []        = [OnlyOld bs1]
+        doDiffWith []        (bs2:xs2) = (OnlyNew bs2):(doDiffWith [] xs2)
+        doDiffWith (bs1:xs1) xs2       =
+            let bs2Maybe = Data.List.find (\x -> x == bs1) xs2
+            in  case bs2Maybe of
+                    Just bs2 -> let subxs2 = Data.List.filter (\x -> x /= bs2) xs2
+                                in  (OldAndNew bs1 bs2):(doDiffWith xs1 subxs2)
+                    Nothing  -> (OnlyOld bs1):(doDiffWith xs1 xs2)
+
+data TextLine = TextLine
+    { lineNumber  :: Integer
+    , lineContent :: L.ByteString
+    }
+instance Eq TextLine where
+  a == b = (lineContent a) == (lineContent b)
+  a /= b = not (a == b)
+instance Ord TextLine where
+  compare a b = compare (lineContent a) (lineContent b)
+  a <  b     = (lineContent a) < (lineContent b)
+  a <= b     = (lineContent a) <= (lineContent b)
+  a >  b     = b < a
+  a >= b     = b <= a
+
+data FilteredDiff = NormalLine (Item TextLine) | Separator
+
+data GitFileContent = NewBinaryFile
+                    | OldBinaryFile
+                    | NewTextFile  [TextLine]
+                    | OldTextFile  [TextLine]
+                    | ModifiedBinaryFile
+                    | ModifiedFile [FilteredDiff]
+                    | UnModifiedFile
+
+data GitFileMode = NewMode        ModePerm
+                 | OldMode        ModePerm
+                 | ModifiedMode   ModePerm ModePerm
+                 | UnModifiedMode ModePerm
+
+data GitFileRef = NewRef        Ref
+                | OldRef        Ref
+                | ModifiedRef   Ref Ref
+                | UnModifiedRef Ref
+
+-- | This is a proposed diff records for a given file.
+-- It contains useful information:
+--   * the filename (with its path in the root project)
+--   * a file diff (with the Data.Algorithm.Patience method)
+--   * the file's mode (i.e. the file priviledge)
+--   * the file's ref
+data GitDiff = GitDiff
+    { hFileName    :: EntPath
+    , hFileContent :: GitFileContent
+    , hFileMode    :: GitFileMode
+    , hFileRef     :: GitFileRef
+    }
+
+-- | A default Diff getter which returns all diff information (Mode, Content
+-- and Binary) with a context of 5 lines.
+--
+-- > getDiff = getDiffWith (defaultDiff 5) []
+getDiff :: Ref
+        -> Ref
+        -> Git
+        -> IO [GitDiff]
+getDiff = getDiffWith (defaultDiff 5) []
+
+-- | A default diff helper. It is an example about how you can write your own
+-- diff helper or you can use it if you want to get all of differences.
+defaultDiff :: Int           -- ^ Number of line for context
+            -> BlobStateDiff
+            -> [GitDiff]     -- ^ Accumulator
+            -> [GitDiff]     -- ^ Accumulator with a new content
+defaultDiff _ (OnlyOld   old    ) acc =
+    let oldMode    = OldMode (bsMode old)
+        oldRef     = OldRef  (bsRef  old)
+        oldContent = case bsContent old of
+                         BinaryContent _ -> OldBinaryFile
+                         FileContent   l -> OldTextFile (Prelude.zipWith TextLine [1..] l)
+    in (GitDiff (bsFilename old) oldContent oldMode oldRef):acc
+defaultDiff _ (OnlyNew       new) acc =
+    let newMode    = NewMode (bsMode new)
+        newRef     = NewRef  (bsRef  new)
+        newContent = case bsContent new of
+                         BinaryContent _ -> NewBinaryFile
+                         FileContent   l -> NewTextFile (Prelude.zipWith TextLine [1..] l)
+    in (GitDiff (bsFilename new) newContent newMode newRef):acc
+defaultDiff context (OldAndNew old new) acc =
+    let mode = if (bsMode old) /= (bsMode new) then ModifiedMode (bsMode old) (bsMode new)
+                                               else UnModifiedMode (bsMode new)
+        ref = if (bsRef old) == (bsRef new) then UnModifiedRef (bsRef new)
+                                            else ModifiedRef (bsRef old) (bsRef new)
+    in case (mode, ref) of
+           ((UnModifiedMode _), (UnModifiedRef _)) -> acc
+           _ -> (GitDiff (bsFilename new) (content ref) mode ref):acc
+    where content :: GitFileRef -> GitFileContent
+          content (UnModifiedRef _) = UnModifiedFile
+          content _                 = createDiff (bsContent old) (bsContent new)
+
+          createDiff :: BlobContent -> BlobContent -> GitFileContent
+          createDiff (FileContent a) (FileContent b) =
+              let linesA = Prelude.zipWith TextLine [1..] a
+                  linesB = Prelude.zipWith TextLine [1..] b
+              in ModifiedFile $ diffGetContext context (diff linesA linesB)
+          createDiff _ _ = ModifiedBinaryFile
+
+-- Used by diffGetContext
+data GitAccu = AccuBottom | AccuTop
+
+-- Context filter
+diffGetContext :: Int -> [Item TextLine] -> [FilteredDiff]
+diffGetContext 0 list = fmap NormalLine list
+diffGetContext context list =
+    let (_, _, filteredDiff) = Prelude.foldr filterContext (0, AccuBottom, []) list
+        theList = removeTrailingBoth filteredDiff
+    in case Prelude.head theList of
+        (NormalLine (Both l1 _)) -> if (lineNumber l1) > 1 then Separator:theList
+                                                           else theList
+        _ -> theList
+    where -- only keep 'context'. The file is annalyzed from the bottom to the top.
+          -- The accumulator here is a tuple3 with (the line counter, the
+          -- direction and the list of elements)
+          filterContext :: (Item TextLine) -> (Int, GitAccu, [FilteredDiff]) -> (Int, GitAccu, [FilteredDiff])
+          filterContext (Both l1 l2) (c, AccuBottom, acc) =
+              if c < context then (c+1, AccuBottom, (NormalLine (Both l1 l2)):acc)
+                             else (c  , AccuBottom, (NormalLine (Both l1 l2))
+                                                    :((Prelude.take (context-1) acc)
+                                                    ++ [Separator]
+                                                    ++ (Prelude.drop (context+1) acc)))
+          filterContext (Both l1 l2) (c, AccuTop, acc) =
+              if c < context then (c+1, AccuTop   , (NormalLine (Both l1 l2)):acc)
+                             else (0  , AccuBottom, (NormalLine (Both l1 l2)):acc)
+          filterContext element (_, _, acc) =
+              (0, AccuTop, (NormalLine element):acc)
+
+          startWithSeparator :: [FilteredDiff] -> Bool
+          startWithSeparator [] = False
+          startWithSeparator (Separator:_) = True
+          startWithSeparator ((NormalLine l):xs) =
+              case l of
+                  (Both _ _) -> startWithSeparator xs
+                  _          -> False
+
+          removeTrailingBoth :: [FilteredDiff] -> [FilteredDiff]
+          removeTrailingBoth diffList =
+              let test = startWithSeparator diffList
+              in  if test then Prelude.tail $ Prelude.dropWhile (\a -> not $ startWithSeparator [a]) diffList
+                          else diffList
diff --git a/Data/Git/Imports.hs b/Data/Git/Imports.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Imports.hs
@@ -0,0 +1,7 @@
+module Data.Git.Imports
+    ( module X
+    ) where
+
+import           Control.Applicative as X
+import           Control.Monad as X
+import           Data.Monoid as X
diff --git a/Data/Git/Internal.hs b/Data/Git/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Internal.hs
@@ -0,0 +1,25 @@
+-- |
+-- Module      : Data.Git.Internal
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Internal
+    ( be32
+    , be16
+    ) where
+
+import Data.Bits
+import Data.Word
+import qualified Data.ByteString as B
+
+be32 :: B.ByteString -> Word32
+be32 b = fromIntegral (B.index b 0) `shiftL` 24
+       + fromIntegral (B.index b 1) `shiftL` 16
+       + fromIntegral (B.index b 2) `shiftL` 8
+       + fromIntegral (B.index b 3)
+
+be16 :: B.ByteString -> Word16
+be16 b = fromIntegral (B.index b 0) `shiftL` 8
+       + fromIntegral (B.index b 1)
diff --git a/Data/Git/Monad.hs b/Data/Git/Monad.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Monad.hs
@@ -0,0 +1,713 @@
+-- |
+-- Module      : Data.Git.Monad
+-- License     : BSD-style
+-- Maintainer  : Nicolas DI PRIMA <nicolas@di-prima.fr>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- Simplifies the Git operation presents in this package.
+--
+-- You can easily access to the usual Git general informations:
+--
+-- * access to Head, Branches or Tags
+-- * direct access to a Commit
+--
+-- This module also defines a convenient Monad to access the whole information
+-- from a Commit: see 'CommitAccessMonad' and 'withCommit'.
+--
+-- You can also easily create a new commit: see 'CommitM' and 'withNewCommit'
+--
+
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+
+module Data.Git.Monad
+    ( -- * GitMonad
+      GitMonad(..)
+    , GitM
+    , withRepo
+    , withCurrentRepo
+      -- ** Operations
+    , Resolvable(..)
+    , branchList
+    , branchWrite
+    , tagList
+    , tagWrite
+    , headGet
+    , headResolv
+    , headSet
+    , getCommit
+
+      -- * Read a commit
+    , CommitAccessM
+    , withCommit
+      -- ** Operations
+    , getAuthor
+    , getCommitter
+    , getParents
+    , getExtras
+    , getEncoding
+    , getMessage
+    , getFile
+    , getDir
+      -- * Create a new Commit
+    , CommitM
+    , withNewCommit
+    , withBranch
+      -- ** Operations
+    , setAuthor
+    , setCommitter
+    , setParents
+    , setExtras
+    , setEncoding
+    , setMessage
+    , setFile
+    , deleteFile
+
+      -- * convenients re-exports
+    , Git.Git
+    , Git.Ref
+    , Git.RefName(..)
+    , Git.Commit(..)
+    , Git.Person(..)
+    ) where
+
+
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.Git as Git
+import qualified Data.Git.Revision as Git
+import qualified Data.Git.Repository as Git
+import qualified Data.Git.Storage.Object as Git
+import           Data.Git.Imports
+import           Data.Git.OS
+
+--import qualified Filesystem.Path as FP
+
+import Data.Set (Set)
+
+-------------------------------------------------------------------------------
+--                              Revision helper                              --
+-------------------------------------------------------------------------------
+
+revisionFromString :: String -> Git.Revision
+revisionFromString = Git.fromString
+
+-- | this is a convenient class to allow a common interface for what user may
+-- need to optain a Ref from a given Resolvable object.
+--
+-- each of this instances is a convenient implementation of what a user would
+-- have to do in order to resolve a branch, a tag or a String.
+--
+-- > resolve (Ref "2ad98b90...2ca") === Ref "2ad98b90...2ca"
+-- > resolve "master"
+-- > resolve "HEAD^^^"
+--
+class Resolvable rev where
+    resolve :: GitMonad m => rev -> m (Maybe Git.Ref)
+instance Resolvable Git.Ref where
+    resolve = return . Just
+instance Resolvable Git.Revision where
+    resolve rev = do
+        git <- getGit
+        liftGit $ Git.resolveRevision git rev
+instance Resolvable String where
+    resolve = resolve . revisionFromString
+instance Resolvable Git.RefName where
+    resolve = resolve . Git.refNameRaw
+
+-------------------------------------------------------------------------------
+--                              GitMonad                                     --
+-------------------------------------------------------------------------------
+
+-- | Basic operations common between the different Monads defined in this
+-- package.
+class (Functor m, Applicative m, Monad m) => GitMonad m where
+    -- | the current Monad must allow access to the current Git
+    getGit :: m Git.Git
+    liftGit :: IO a -> m a
+
+branchList :: GitMonad git => git (Set Git.RefName)
+branchList = getGit >>= liftGit . Git.branchList
+
+branchWrite :: GitMonad git => Git.RefName -> Git.Ref -> git ()
+branchWrite rn ref = do
+    git <- getGit
+    liftGit $ Git.branchWrite git rn ref
+
+tagList :: GitMonad git => git (Set Git.RefName)
+tagList = getGit >>= liftGit . Git.tagList
+
+tagWrite :: GitMonad git => Git.RefName -> Git.Ref -> git ()
+tagWrite rn ref = do
+    git <- getGit
+    liftGit $ Git.tagWrite git rn ref
+
+headGet :: GitMonad git => git (Either Git.Ref Git.RefName)
+headGet = getGit >>= liftGit . Git.headGet
+
+headResolv :: GitMonad git => git (Maybe Git.Ref)
+headResolv = do
+    e <- headGet
+    case e of
+        Left ref -> resolve ref
+        Right v  -> resolve v
+
+headSet :: GitMonad git => Either Git.Ref Git.RefName -> git ()
+headSet e = do
+    git <- getGit
+    liftGit $ Git.headSet git e
+
+getCommit :: (GitMonad git, Resolvable ref) => ref -> git (Maybe Git.Commit)
+getCommit r = do
+    mRef <- resolve r
+    case mRef of
+        Nothing  -> return Nothing
+        Just ref -> do
+            git <- getGit
+            liftGit $ Git.getCommitMaybe git ref
+
+setObject :: (GitMonad git, Git.Objectable obj) => obj -> git Git.Ref
+setObject obj = do
+    git <- getGit
+    liftGit $ Git.setObject git $ Git.toObject obj
+
+getObject :: (GitMonad git, Resolvable ref)
+          => ref
+          -> Bool
+          -> git (Maybe Git.Object)
+getObject rev resolvDelta = do
+    git <- getGit
+    mRef <- resolve rev
+    case mRef of
+        Nothing  -> return Nothing
+        Just ref -> liftGit $ Git.getObject git ref resolvDelta
+
+workTreeNew :: GitMonad git => git Git.WorkTree
+workTreeNew = liftGit Git.workTreeNew
+
+workTreeFrom :: GitMonad git => Git.Ref -> git Git.WorkTree
+workTreeFrom ref = liftGit $ Git.workTreeFrom ref
+
+workTreeFlush :: GitMonad git => Git.WorkTree -> git Git.Ref
+workTreeFlush tree = do
+    git <- getGit
+    liftGit $ Git.workTreeFlush git tree
+
+resolvPath :: (GitMonad git, Resolvable ref)
+           => ref -- ^ the commit Ref, Revision ("master", "HEAD^^" or a ref...)
+           -> Git.EntPath
+           -> git (Maybe Git.Ref)
+resolvPath commitRev entPath = do
+    git <- getGit
+    mRef <- resolve commitRev
+    case mRef of
+        Nothing  -> return Nothing
+        Just ref -> liftGit $ Git.resolvePath git ref entPath
+
+-------------------------------------------------------------------------------
+--                                                                           --
+-------------------------------------------------------------------------------
+
+data Result ctx a
+    = ResultSuccess !ctx !a
+    | ResultFailure !String
+
+-------------------------------------------------------------------------------
+--                                 GitM                                      --
+-------------------------------------------------------------------------------
+
+data GitContext = GitContext
+    { gitContextGit  :: !Git.Git
+    }
+
+newtype GitM a = GitM
+    { runGitM :: GitContext -> IO (Result GitContext a)
+    }
+
+instance Functor GitM where
+    fmap = fmapGitM
+
+instance Applicative GitM where
+    pure  = returnGitM
+    (<*>) = appendGitM
+
+instance Monad GitM where
+    return = returnGitM
+    (>>=)  = bindGitM
+    fail   = failGitM
+
+instance GitMonad GitM where
+    getGit  = getGitM
+    liftGit = liftGitM
+
+fmapGitM :: (a -> b) -> GitM a -> GitM b
+fmapGitM f m = GitM $ \ctx -> do
+    r <- runGitM m ctx
+    return $ case r of
+        ResultSuccess ctx' v -> ResultSuccess ctx' (f v)
+        ResultFailure err    -> ResultFailure err
+
+returnGitM :: a -> GitM a
+returnGitM v = GitM $ \ctx -> return (ResultSuccess ctx v)
+
+appendGitM :: GitM (a -> b) -> GitM a -> GitM b
+appendGitM m1f m2 = m1f >>= \f -> m2 >>= \v -> return (f v)
+
+bindGitM :: GitM a -> (a -> GitM b) -> GitM b
+bindGitM m fm = GitM $ \ctx -> do
+    r <- runGitM m ctx
+    case r of
+        ResultSuccess ctx' v -> runGitM (fm v) ctx'
+        ResultFailure err    -> return (ResultFailure err)
+
+failGitM :: String -> GitM a
+failGitM msg = GitM $ \_ -> return (ResultFailure msg)
+
+getGitM :: GitM Git.Git
+getGitM = GitM $ \ctx -> return (ResultSuccess ctx (gitContextGit ctx))
+
+liftGitM :: IO a -> GitM a
+liftGitM f = GitM $ \ctx -> ResultSuccess ctx <$> f
+
+executeGitM :: Git.Git -> GitM a -> IO (Either String a)
+executeGitM git m = do
+    r <- runGitM m $ GitContext git
+    return $ case r of
+        ResultSuccess _   v -> Right v
+        ResultFailure err   -> Left err
+
+withRepo :: LocalPath -> GitM a -> IO (Either String a)
+withRepo repoPath m = Git.withRepo repoPath (\git -> executeGitM git m)
+
+withCurrentRepo :: GitM a -> IO (Either String a)
+withCurrentRepo m = Git.withCurrentRepo (\git -> executeGitM git m)
+
+
+-------------------------------------------------------------------------------
+--                             CommitAccessM                                 --
+-------------------------------------------------------------------------------
+
+data CommitAccessContext = CommitAccessContext
+    { commitAccessContextCommit :: !Git.Commit
+    , commitAccessContextRef    :: !Git.Ref
+    }
+
+-- | ReadOnly operations on a given commit
+newtype CommitAccessM  a = CommitAccessM
+    { runCommitAccessM :: forall git . GitMonad git => CommitAccessContext -> git (Result CommitAccessContext a)
+    }
+
+instance Functor CommitAccessM where
+    fmap = fmapCommitAccessM
+
+instance Applicative CommitAccessM where
+    pure  = returnCommitAccessM
+    (<*>) = appendCommitAccessM
+
+instance Monad CommitAccessM where
+    return = returnCommitAccessM
+    (>>=)  = bindCommitAccessM
+    fail   = failCommitAccessM
+
+instance GitMonad CommitAccessM where
+    getGit  = getCommitAccessM
+    liftGit = liftCommitAccessM
+
+fmapCommitAccessM :: (a -> b) -> CommitAccessM a -> CommitAccessM b
+fmapCommitAccessM f m = CommitAccessM $ \ctx -> do
+    r <- runCommitAccessM m ctx
+    return $ case r of
+        ResultSuccess ctx' v -> ResultSuccess ctx' (f v)
+        ResultFailure err    -> ResultFailure err
+
+returnCommitAccessM :: a -> CommitAccessM a
+returnCommitAccessM v = CommitAccessM $ \ctx -> return (ResultSuccess ctx v)
+
+appendCommitAccessM :: CommitAccessM (a -> b) -> CommitAccessM a -> CommitAccessM b
+appendCommitAccessM m1f m2 = m1f >>= \f -> m2 >>= \v -> return (f v)
+
+bindCommitAccessM :: CommitAccessM a -> (a -> CommitAccessM b) -> CommitAccessM b
+bindCommitAccessM m fm = CommitAccessM $ \ctx -> do
+    r <- runCommitAccessM m ctx
+    case r of
+        ResultSuccess ctx' v -> runCommitAccessM (fm v) ctx'
+        ResultFailure err    -> return (ResultFailure err)
+
+failCommitAccessM :: String -> CommitAccessM a
+failCommitAccessM msg = CommitAccessM $ \_ -> return (ResultFailure msg)
+
+getCommitAccessM :: CommitAccessM Git.Git
+getCommitAccessM = CommitAccessM $ \ctx -> ResultSuccess ctx <$> getGit
+
+liftCommitAccessM :: IO a -> CommitAccessM a
+liftCommitAccessM f = CommitAccessM $ \ctx -> ResultSuccess ctx <$> (liftGit f)
+
+-- Operations -----------------------------------------------------------------
+
+withCommitAccessContext :: (CommitAccessContext -> a) -> CommitAccessM a
+withCommitAccessContext operation = CommitAccessM $ \ctx ->
+    return $ ResultSuccess ctx $ operation ctx
+
+getAuthor :: CommitAccessM Git.Person
+getAuthor = withCommitAccessContext (Git.commitAuthor . commitAccessContextCommit)
+
+getCommitter :: CommitAccessM Git.Person
+getCommitter = withCommitAccessContext (Git.commitCommitter . commitAccessContextCommit)
+
+getParents :: CommitAccessM [Git.Ref]
+getParents = withCommitAccessContext (Git.commitParents . commitAccessContextCommit)
+
+getExtras :: CommitAccessM [Git.CommitExtra]
+getExtras = withCommitAccessContext (Git.commitExtras . commitAccessContextCommit)
+
+getEncoding :: CommitAccessM (Maybe ByteString)
+getEncoding = withCommitAccessContext (Git.commitEncoding . commitAccessContextCommit)
+
+getMessage :: CommitAccessM ByteString
+getMessage = withCommitAccessContext (Git.commitMessage . commitAccessContextCommit)
+
+getContextRef_ :: CommitAccessM Git.Ref
+getContextRef_ = withCommitAccessContext commitAccessContextRef
+
+getContextObject_ :: Git.EntPath -> CommitAccessM (Maybe Git.Object)
+getContextObject_ fp = do
+    commitRef <- getContextRef_
+    mRef <- resolvPath commitRef fp
+    case mRef of
+        Nothing  -> return Nothing
+        Just ref -> getObject ref True
+
+-- | get the content of the file at the given Path
+--
+-- if the given Path is not a file or does not exist,
+-- the function returns Nothing.
+getFile :: Git.EntPath -> CommitAccessM (Maybe BL.ByteString)
+getFile fp = do
+    mObj <- getContextObject_ fp
+    return $ case mObj of
+        Nothing  -> Nothing
+        Just obj -> case Git.objectToBlob obj of
+            Nothing -> Nothing
+            Just b  -> Just $ Git.blobGetContent b
+
+-- | list the element present in the Given Directory Path
+--
+-- if the given Path is not a directory or does not exist,
+-- the function returns Nothing.
+getDir :: Git.EntPath -> CommitAccessM (Maybe [Git.EntName])
+getDir fp = do
+    mObj <- getContextObject_ fp
+    return $ case mObj of
+        Nothing  -> Nothing
+        Just obj -> case Git.objectToTree obj of
+            Nothing   -> Nothing
+            Just tree -> Just $ map (\(_, n, _) -> n) $ Git.treeGetEnts tree
+
+-- | open a commit in the current GitMonad
+--
+-- Read commit's info (Author, Committer, message...) or Commit's Tree.
+--
+-- > withCurrentRepo $
+-- >    withCommit "master" $ do
+-- >        -- print the commit's author information
+-- >        author <- getAuthor
+-- >        liftGit $ print author
+-- >
+-- >        -- print the list of files|dirs in the root directory
+-- >        l <- getDir []
+-- >        liftGit $ print l
+--
+withCommit :: (Resolvable ref, GitMonad git)
+           => ref
+                -- ^ the commit revision or reference to open
+           -> CommitAccessM a
+           -> git a
+withCommit rev m = do
+    mRef <- resolve rev
+    case mRef of
+        Nothing -> fail "revision does not exist"
+        Just ref -> do
+            mCommit <- getCommit ref
+            case mCommit of
+                Nothing -> fail $ "the given ref does not exist or is not a commit"
+                Just commit -> do
+                    let ctx = CommitAccessContext
+                                { commitAccessContextCommit = commit
+                                , commitAccessContextRef    = ref
+                                }
+                    r <- runCommitAccessM m ctx
+                    case r of
+                        ResultFailure err   -> fail err
+                        ResultSuccess _   a -> return a
+
+-------------------------------------------------------------------------------
+--                                 CommitM                                      --
+-------------------------------------------------------------------------------
+
+data CommitContext = CommitContext
+    { commitContextAuthor    :: !Git.Person
+    , commitContextCommitter :: !Git.Person
+    , commitContextParents   :: ![Git.Ref]
+    , commitContextExtras    :: ![Git.CommitExtra]
+    , commitContextEncoding  :: !(Maybe ByteString)
+    , commitContextMessage   :: !ByteString
+    , commitContextTree      :: !Git.WorkTree
+    }
+
+newtype CommitM  a = CommitM
+    { runCommitM :: forall git . GitMonad git => CommitContext -> git (Result CommitContext a)
+    }
+
+instance Functor CommitM where
+    fmap = fmapCommitM
+
+instance Applicative CommitM where
+    pure  = returnCommitM
+    (<*>) = appendCommitM
+
+instance Monad CommitM where
+    return = returnCommitM
+    (>>=)  = bindCommitM
+    fail   = failCommitM
+
+instance GitMonad CommitM where
+    getGit  = getCommitM
+    liftGit = liftCommitM
+
+fmapCommitM :: (a -> b) -> CommitM a -> CommitM b
+fmapCommitM f m = CommitM $ \ctx -> do
+    r <- runCommitM m ctx
+    return $ case r of
+        ResultSuccess ctx' v -> ResultSuccess ctx' (f v)
+        ResultFailure err    -> ResultFailure err
+
+returnCommitM :: a -> CommitM a
+returnCommitM v = CommitM $ \ctx -> return (ResultSuccess ctx v)
+
+appendCommitM :: CommitM (a -> b) -> CommitM a -> CommitM b
+appendCommitM m1f m2 = m1f >>= \f -> m2 >>= \v -> return (f v)
+
+bindCommitM :: CommitM a -> (a -> CommitM b) -> CommitM b
+bindCommitM m fm = CommitM $ \ctx -> do
+    r <- runCommitM m ctx
+    case r of
+        ResultSuccess ctx' v -> runCommitM (fm v) ctx'
+        ResultFailure err    -> return (ResultFailure err)
+
+failCommitM :: String -> CommitM a
+failCommitM msg = CommitM $ \_ -> return (ResultFailure msg)
+
+getCommitM :: CommitM Git.Git
+getCommitM = CommitM $ \ctx -> ResultSuccess ctx <$> getGit
+
+liftCommitM :: IO a -> CommitM a
+liftCommitM f = CommitM $ \ctx -> ResultSuccess ctx <$> (liftGit f)
+
+-- Operations -----------------------------------------------------------------
+
+commitUpdateContext :: (CommitContext -> IO (CommitContext, a)) -> CommitM a
+commitUpdateContext operation = CommitM $ \ctx -> do
+    (ctx', r) <- liftGit $ operation ctx
+    return (ResultSuccess ctx' r)
+
+-- | replace the Commit's Author
+setAuthor :: Git.Person -> CommitM ()
+setAuthor p = commitUpdateContext $ \ctx -> return (ctx { commitContextCommitter = p }, ())
+
+-- | replace the Commit's Committer
+setCommitter :: Git.Person -> CommitM ()
+setCommitter p = commitUpdateContext $ \ctx -> return (ctx { commitContextCommitter = p }, ())
+
+-- | replace the Commit's Parents
+setParents :: [Git.Ref] -> CommitM ()
+setParents l = commitUpdateContext $ \ctx -> return (ctx { commitContextParents = l }, ())
+
+-- | replace the Commit's Extras
+setExtras :: [Git.CommitExtra] -> CommitM ()
+setExtras l = commitUpdateContext $ \ctx -> return (ctx { commitContextExtras = l }, ())
+
+-- | replace the Commit's encoding
+setEncoding :: Maybe ByteString -> CommitM ()
+setEncoding e = commitUpdateContext $ \ctx -> return (ctx { commitContextEncoding = e }, ())
+
+-- | replace the Commit's message with the new given message.
+setMessage :: ByteString -> CommitM ()
+setMessage msg = commitUpdateContext $ \ctx -> return (ctx { commitContextMessage = msg }, ())
+
+setContextObject_ :: Git.Objectable object
+                  => Git.EntPath
+                  -> (Git.EntType, object)
+                  -> CommitM ()
+setContextObject_ path (t, obj) = do
+    ref <- setObject obj
+    git <- getGit
+    commitUpdateContext $ \ctx -> do
+        Git.workTreeSet git (commitContextTree ctx) path (t, ref)
+        return (ctx, ())
+
+-- | add a new file in in the Commit's Working Tree
+setFile :: Git.EntPath
+        -> BL.ByteString
+        -> CommitM ()
+setFile path bl = setContextObject_ path (Git.EntFile , Git.Blob bl)
+
+-- | delete a file from the Commit's Working Tree.
+deleteFile :: Git.EntPath -> CommitM ()
+deleteFile path = do
+    git <- getGit
+    commitUpdateContext $ \ctx -> do
+        Git.workTreeDelete git (commitContextTree ctx) path
+        return (ctx, ())
+
+-- | create a new commit in the current GitMonad
+--
+-- The commit is pre-filled with the following default values:
+--
+-- * author and committer are the same
+-- * the commit's parents is an empty list
+-- * there is no commit encoding
+-- * the commit's extras is an empty list
+-- * the commit message is an empty ByteString
+-- * the working tree is a new empty Tree or the Tree associated to the
+--   given Revision or Ref.
+--
+-- You can update these values with the commit setters (setFile, setAuthor...)
+--
+-- Example:
+--
+-- > withCurrentRepo $
+-- >    (r, ()) <- withNewCommit person Nothing $ do
+-- >        setMessage "inital commit"
+-- >        setFile ["README.md"] "# My awesome project\n\nthis is a new project\n"
+-- >    branchWrite "master" r
+-- >
+--
+-- you can also continue the work on a same branch. In this case the commit's
+-- parent is already set to the Reference associated to the revision.
+-- You can, change the parents if you wish to erase, or replace, this value.
+--
+-- > withCurrentRepo $
+-- >    readmeContent <- withCommit (Just "master") $ getFile ["README.md"]
+-- >    (r, ()) <- withNewCommit person (Just "master") $ do
+-- >        setMessage "update the README"
+-- >        setFile ["README.md"] $ readmeContent <> "just add some more description\n"
+-- >    branchWrite "master" r
+--
+withNewCommit :: (GitMonad git, Resolvable rev)
+              => Git.Person
+                -- ^ by default a commit must have an Author and a Committer.
+                --
+                -- The given value will be given to both Author and Committer.
+              -> Maybe rev
+                -- ^ it is possible to prepopulate the Working Tree with a
+                -- given Ref's Tree.
+              -> CommitM a
+                -- ^ the action to perform in the new commit (set files,
+                -- Person, encoding or extras)
+              -> git (Git.Ref, a)
+withNewCommit p mPrec m = do
+    workTree <- case mPrec of
+                    Nothing -> workTreeNew
+                    Just r -> do
+                        mc <- getCommit r
+                        case mc of
+                            Nothing -> fail "the given revision does not exist or is not a commit"
+                            Just c  -> workTreeFrom (Git.commitTreeish c)
+    parents <- case mPrec of
+                    Nothing -> return []
+                    Just r  -> do
+                        mr <- resolve r
+                        return $ case mr of
+                            Nothing -> []
+                            Just ref -> [ref]
+    let ctx = CommitContext
+                { commitContextAuthor    = p
+                , commitContextCommitter = p
+                , commitContextParents   = parents
+                , commitContextExtras    = []
+                , commitContextEncoding  = Nothing
+                , commitContextMessage   = B.empty
+                , commitContextTree      = workTree
+                }
+    r <- runCommitM m ctx
+    case r of
+        ResultFailure err    -> fail err
+        ResultSuccess ctx' a -> do
+            treeRef <- workTreeFlush (commitContextTree ctx')
+            let commit = Git.Commit
+                            { Git.commitTreeish   = treeRef
+                            , Git.commitParents   = commitContextParents ctx'
+                            , Git.commitAuthor    = commitContextAuthor ctx'
+                            , Git.commitCommitter = commitContextCommitter ctx'
+                            , Git.commitEncoding  = commitContextEncoding ctx'
+                            , Git.commitExtras    = commitContextExtras ctx'
+                            , Git.commitMessage   = commitContextMessage ctx'
+                            }
+            ref <- setObject commit
+            return (ref, a)
+
+-- | create or continue to work on a branch
+--
+-- This is a convenient function to create or to linearily work  on a branch.
+-- This function applies a first Collect of information on the parent commit
+-- (the actual branch's commit). Then it creates a new commit and update
+-- the branch to point to this commit.
+--
+-- for example:
+--
+-- @
+-- withCurrentRepo $
+--     withBranch person "master" True
+--         (getAuthor)
+--         (maybe (setMessage "initial commit on this branch")
+--                (\author -> setMessage $ "continue the great work of " ++ show (personName author))
+--         )
+-- @
+--
+withBranch :: GitMonad git
+           => Git.Person
+                -- ^ the default Author and Committer (see 'withNewCommit')
+           -> Git.RefName
+                -- ^ the branch to work on
+           -> Bool
+                -- ^ propopulate the parent's tree (if it exists) in the
+                -- new created commit.
+                --
+                -- In any cases, if the branch already exists, the new commit
+                -- parent will be filled with the result of ('resolv' "branchName")
+           -> (CommitAccessM a)
+                -- ^ the action to performs in the parent's new commit if it exists.
+           -> (Maybe a -> CommitM b)
+                -- ^ the action to performs in the new commit
+                --
+                -- the argument is the result of the action on the parent commit.
+                --
+                -- Nothing if the parent does not exist.
+           -> git (Git.Ref, b)
+withBranch p branchName keepTree actionParent actionNew = do
+    -- attempt to resolve the branch
+    mRefParent <- resolve branchName
+
+    -- configure the precedency of the tree and the action in the new commit
+    (mRefTree, actionInCommit) <- case mRefParent of
+        -- in the case the branch does not exist already: there is not precedency
+        Nothing -> return (Nothing, actionNew Nothing)
+        -- if the branch exists
+        Just refParent -> do
+            -- performs the action in the parent commit
+            a <- withCommit refParent actionParent
+            return $ if keepTree
+                -- if user has choosen to prepopulate the Tree with the
+                -- parent's tree we prepopulate the tree.
+                then (Just refParent, actionNew $ Just a)
+                -- else, we make sure the parent is at least setted
+                else (Nothing, setParents [refParent] >> actionNew (Just a))
+    -- create the new commit
+    (ref, b) <- withNewCommit p (mRefTree) actionInCommit
+    -- write the branch
+    branchWrite branchName ref
+    return (ref, b)
diff --git a/Data/Git/Named.hs b/Data/Git/Named.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Named.hs
@@ -0,0 +1,178 @@
+-- |
+-- Module      : Data.Git.Named
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- Manipulation of named references
+-- * reading packed-refs file
+-- * reading single heads/tags/remote file
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+module Data.Git.Named
+    ( RefSpecTy(..)
+    , RefContentTy(..)
+    , RefName(..)
+    , readPackedRefs
+    , PackedRefs(..)
+    -- * manipulating loosed name references
+    , existsRefFile
+    , writeRefFile
+    , readRefFile
+    -- * listings looses name references
+    , looseHeadsList
+    , looseTagsList
+    , looseRemotesList
+    ) where
+
+import Data.String
+import Data.Git.Path
+import Data.Git.Ref
+import Data.Git.Imports
+import Data.Git.OS
+import Data.List (isPrefixOf)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.UTF8 as UTF8
+
+-- | Represent a named specifier.
+data RefSpecTy = RefHead
+               | RefOrigHead
+               | RefFetchHead
+               | RefBranch RefName
+               | RefTag RefName
+               | RefRemote RefName
+               | RefPatches String
+               | RefStash
+               | RefOther String
+               deriving (Show,Eq,Ord)
+
+-- | content of a ref file.
+data RefContentTy = RefDirect Ref
+                  | RefLink   RefSpecTy
+                  | RefContentUnknown B.ByteString
+                  deriving (Show,Eq)
+
+newtype RefName = RefName { refNameRaw :: String }
+    deriving (Show,Eq,Ord)
+
+instance IsString RefName where
+    fromString s
+        | isValidRefName s = RefName s
+        | otherwise        = error ("invalid RefName " ++ show s)
+
+isValidRefName :: String -> Bool
+isValidRefName s = not (or $ map isBadChar s)
+  where isBadChar :: Char -> Bool
+        isBadChar c = c <= ' ' || c >= toEnum 0x7f || c `elem` badAscii
+        badAscii = [ '~', '^', ':', '\\', '*', '?', '[' ]
+
+toRefTy :: String -> RefSpecTy
+toRefTy s
+    | "refs/tags/" `isPrefixOf` s    = RefTag $ RefName $ drop 10 s
+    | "refs/heads/" `isPrefixOf` s   = RefBranch $ RefName $ drop 11 s
+    | "refs/remotes/" `isPrefixOf` s = RefRemote $ RefName $ drop 13 s
+    | "refs/patches/" `isPrefixOf` s = RefPatches $ drop 13 s
+    | "refs/stash" == s              = RefStash
+    | "HEAD" == s                    = RefHead
+    | "ORIG_HEAD" == s               = RefOrigHead
+    | "FETCH_HEAD" == s              = RefFetchHead
+    | otherwise                      = RefOther $ s
+
+fromRefTy :: RefSpecTy -> String
+fromRefTy (RefBranch h)  = "refs/heads/" ++ refNameRaw h
+fromRefTy (RefTag h)     = "refs/tags/" ++ refNameRaw h
+fromRefTy (RefRemote h)  = "refs/remotes/" ++ refNameRaw h
+fromRefTy (RefPatches h) = "refs/patches/" ++ h
+fromRefTy RefStash       = "refs/stash"
+fromRefTy RefHead        = "HEAD"
+fromRefTy RefOrigHead    = "ORIG_HEAD"
+fromRefTy RefFetchHead   = "FETCH_HEAD"
+fromRefTy (RefOther h)   = h
+
+toPath :: LocalPath -> RefSpecTy -> LocalPath
+toPath gitRepo (RefBranch h)  = gitRepo </> "refs" </> "heads" </> fromString (refNameRaw h)
+toPath gitRepo (RefTag h)     = gitRepo </> "refs" </> "tags" </> fromString (refNameRaw h)
+toPath gitRepo (RefRemote h)  = gitRepo </> "refs" </> "remotes" </> fromString (refNameRaw h)
+toPath gitRepo (RefPatches h) = gitRepo </> "refs" </> "patches" </> fromString h
+toPath gitRepo RefStash       = gitRepo </> "refs" </> "stash"
+toPath gitRepo RefHead        = gitRepo </> "HEAD"
+toPath gitRepo RefOrigHead    = gitRepo </> "ORIG_HEAD"
+toPath gitRepo RefFetchHead   = gitRepo </> "FETCH_HEAD"
+toPath gitRepo (RefOther h)   = gitRepo </> fromString h
+
+data PackedRefs a = PackedRefs
+    { packedRemotes :: a
+    , packedBranchs :: a
+    , packedTags    :: a
+    }
+
+readPackedRefs :: LocalPath
+               -> ([(RefName, Ref)] -> a)
+               -> IO (PackedRefs a)
+readPackedRefs gitRepo constr = do
+    exists <- isFile (packedRefsPath gitRepo)
+    if exists then readLines else return $ finalize emptyPackedRefs
+  where emptyPackedRefs = PackedRefs [] [] []
+        readLines = finalize . foldl accu emptyPackedRefs . BC.lines <$> readBinaryFile (packedRefsPath gitRepo)
+        finalize (PackedRefs a b c) = PackedRefs (constr a) (constr b) (constr c)
+        accu a l
+            | "#" `BC.isPrefixOf` l = a
+            | otherwise =
+                let (ref, r) = B.splitAt 40 l
+                    name     = UTF8.toString $ B.tail r
+                 in case toRefTy name of
+                        -- accumulate tag, branch and remotes
+                        RefTag refname    -> a { packedTags    = (refname, fromHex ref) : packedTags a }
+                        RefBranch refname -> a { packedBranchs = (refname, fromHex ref) : packedBranchs a }
+                        RefRemote refname -> a { packedRemotes = (refname, fromHex ref) : packedRemotes a }
+                        -- anything else that shouldn't be there get dropped on the floor
+                        _                 -> a
+
+-- | list all the loose refs available recursively from a directory starting point
+listRefs :: LocalPath -> IO [RefName]
+listRefs root = listRefsAcc [] root
+  where listRefsAcc acc dir = do
+            files <- listDirectory dir
+            getRefsRecursively dir acc files
+        getRefsRecursively _   acc []     = return acc
+        getRefsRecursively dir acc (x:xs) = do
+            isDir <- isDirectory x
+            extra <- if isDir
+                        then listRefsAcc [] dir
+                        else let r = UTF8.toString $ localPathEncode $ stripRoot x
+                              in if isValidRefName r
+                                    then return [fromString r]
+                                    else return []
+            getRefsRecursively dir (extra ++ acc) xs
+        stripRoot p = maybe (error "stripRoot invalid") id $ stripPrefix root p
+
+looseHeadsList :: LocalPath -> IO [RefName]
+looseHeadsList gitRepo = listRefs (headsPath gitRepo)
+
+looseTagsList :: LocalPath -> IO [RefName]
+looseTagsList gitRepo = listRefs (tagsPath gitRepo)
+
+looseRemotesList :: LocalPath -> IO [RefName]
+looseRemotesList gitRepo = listRefs (remotesPath gitRepo)
+
+existsRefFile :: LocalPath -> RefSpecTy -> IO Bool
+existsRefFile gitRepo specty = isFile $ toPath gitRepo specty
+
+writeRefFile :: LocalPath -> RefSpecTy -> RefContentTy -> IO ()
+writeRefFile gitRepo specty refcont = do
+    createParentDirectory filepath
+    writeBinaryFile filepath $ fromRefContent refcont
+    where filepath = toPath gitRepo specty
+          fromRefContent (RefLink link)        = B.concat ["ref: ", UTF8.fromString $ fromRefTy link, B.singleton 0xa]
+          fromRefContent (RefDirect ref)       = B.concat [toHex ref, B.singleton 0xa]
+          fromRefContent (RefContentUnknown c) = c
+
+readRefFile :: LocalPath -> RefSpecTy -> IO RefContentTy
+readRefFile gitRepo specty = toRefContent <$> readBinaryFile filepath
+    where filepath = toPath gitRepo specty
+          toRefContent content
+            | "ref: " `B.isPrefixOf` content = RefLink $ toRefTy $ UTF8.toString $ head $ BC.lines $ B.drop 5 content
+            | B.length content < 42          = RefDirect $ fromHex $ B.take 40 content
+            | otherwise                      = RefContentUnknown content
diff --git a/Data/Git/OS.hs b/Data/Git/OS.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/OS.hs
@@ -0,0 +1,101 @@
+-- |
+-- Module      : Data.Git.OS
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- dealing with operating system / IO related stuff
+-- like file on disk
+module Data.Git.OS
+    ( LocalPath
+    -- * re-export
+    , getHomeDirectory
+    , getWorkingDirectory
+    , (</>)
+    , listDirectoryFilename
+    , listDirectory
+    , openFile
+    , readFile
+    , readTextFile
+    , writeTextFile
+    , readBinaryFile
+    , readBinaryFileLazy
+    , writeBinaryFile
+    , hClose
+    , IOMode(..)
+    , Handle
+    , createParentDirectory
+    , createDirectory
+    , writeFile
+    , isFile
+    , isDirectory
+    , valid
+    , getSize
+    , MTime(..)
+    , timeZero
+    , getMTime
+    , withFile
+    , rename
+    , removeFile
+    , getEnvAsPath
+    , absolute
+    , parent
+    , stripPrefix
+    , localPathEncode
+    , localPathDecode
+    ) where
+
+import           Data.Git.Imports
+import           Filesystem.Path.CurrentOS
+import           Filesystem hiding (readTextFile, writeTextFile)
+import qualified Filesystem.Path.Rules as Rules
+import           System.PosixCompat.Files (getFileStatus, modificationTime)
+import           System.PosixCompat.Types (EpochTime)
+import           System.IO (hClose)
+import           System.Environment
+import           Prelude hiding (FilePath, writeFile, readFile)
+import qualified Prelude
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+
+type LocalPath = FilePath
+
+listDirectoryFilename :: LocalPath -> IO [String]
+listDirectoryFilename dir =
+     map (Rules.encodeString Rules.posix . filename) <$> listDirectory dir
+
+createParentDirectory :: LocalPath -> IO ()
+createParentDirectory filepath = createTree $ parent filepath
+
+readTextFile :: LocalPath -> IO String
+readTextFile filepath = Prelude.readFile (encodeString filepath)
+
+writeTextFile :: LocalPath -> String -> IO ()
+writeTextFile filepath = Prelude.writeFile (encodeString filepath)
+
+newtype MTime = MTime EpochTime deriving (Eq,Ord)
+
+timeZero :: EpochTime
+timeZero = 0
+
+getMTime :: LocalPath -> IO MTime
+getMTime filepath = MTime . modificationTime <$> getFileStatus (encodeString filepath)
+
+getEnvAsPath :: String -> IO LocalPath
+getEnvAsPath envName = Rules.decodeString Rules.posix <$> getEnv envName
+
+localPathDecode :: B.ByteString -> LocalPath
+localPathDecode = Rules.decode Rules.posix
+
+localPathEncode :: LocalPath -> B.ByteString
+localPathEncode = Rules.encode Rules.posix
+
+readBinaryFile :: LocalPath -> IO B.ByteString
+readBinaryFile lp = readFile lp
+
+writeBinaryFile :: LocalPath -> B.ByteString -> IO ()
+writeBinaryFile = writeFile
+
+readBinaryFileLazy :: LocalPath -> IO L.ByteString
+readBinaryFileLazy lp = L.readFile (encodeString lp)
diff --git a/Data/Git/Parser.hs b/Data/Git/Parser.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Parser.hs
@@ -0,0 +1,131 @@
+module Data.Git.Parser
+    (
+      Parser
+    , P.Result(..)
+    , eitherParse
+    , eitherParseChunks
+    , maybeParse
+    , maybeParseChunks
+    -- * Specific functions
+    , word32
+    , ref
+    , referenceBin
+    , referenceHex
+    , vlf
+    , tillEOL
+    , skipEOL
+    , skipASCII
+    , takeUntilASCII
+    , decimal
+    , takeWhile1
+    , string
+    -- * Simple re-export
+    , P.anyByte
+    , P.byte
+    , P.bytes
+    , P.take
+    , P.takeWhile
+    , P.parse
+    , P.parseFeed
+    , P.takeAll
+    , P.hasMore
+    ) where
+
+import qualified Data.ByteArray.Parse as P
+import           Data.ByteArray (ByteArray)
+
+import           Data.Bits
+import           Data.Word (Word8, Word32)
+import           Data.Char (isDigit)
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+
+import           Data.Git.Ref
+import           Data.Git.Internal
+import           Data.Git.Imports
+
+type Parser = P.Parser B.ByteString
+
+vlf :: Parser [Word8]
+vlf = do
+    bs <- P.takeWhile (\w -> w `testBit` 7)
+    l  <- P.anyByte
+    return $ (map (\w -> w `clearBit` 7) $ B.unpack bs) ++ [l]
+
+word32 :: Parser Word32
+word32 = be32 <$> P.take 4
+
+ref, referenceBin, referenceHex :: Parser Ref
+ref = referenceBin
+referenceBin = fromBinary <$> P.take 20
+referenceHex = fromHex <$> P.take 40
+
+decimal :: (Read n, Num n) => Parser n
+decimal = toNum <$> P.takeWhile (\x -> isDigit $ toEnum (fromIntegral x))
+  where toNum = read . BC.unpack
+
+string :: B.ByteString -> Parser ()
+string = P.bytes
+
+maybeParse :: Parser a -> B.ByteString -> Maybe a
+maybeParse f = toMaybe . P.parse f
+
+maybeParseChunks :: Parser a -> [B.ByteString] -> Maybe a
+maybeParseChunks p []     = toMaybe $ P.parse p B.empty
+maybeParseChunks p (i:is) = loop (P.parse p i) is
+  where
+    loop (P.ParseOK _ a) []     = Just a
+    loop (P.ParseMore c) []     = toMaybe $ c Nothing
+    loop (P.ParseMore c) (x:xs) = loop (c $ Just x) xs
+    loop _               _      = Nothing
+
+toMaybe :: P.Result t a -> Maybe a
+toMaybe (P.ParseOK _ a) = Just a
+toMaybe (P.ParseMore c) = toMaybe (c Nothing)
+toMaybe _               = Nothing
+
+eitherParse :: Parser a -> B.ByteString -> Either String a
+eitherParse f = toEither . P.parse f
+
+eitherParseChunks :: Show a => Parser a -> [B.ByteString] -> Either String a
+eitherParseChunks p []     = toEither $ P.parse p B.empty
+eitherParseChunks p (i:is) = loop (P.parse p i) is
+  where
+    loop (P.ParseOK _ a) []     = Right a
+    loop (P.ParseMore c) []     = toEither $ c Nothing
+    loop (P.ParseMore c) (x:xs) = loop (c $ Just x) xs
+    loop ps              l      = Left ("eitherParseChunk: error: " <> show ps <> " : " <> show l)
+
+toEither :: P.Result t b -> Either String b
+toEither (P.ParseOK _ a) = Right a
+toEither (P.ParseFail e) = Left e
+toEither (P.ParseMore c) = toEither (c Nothing)
+
+takeUntilASCII :: ByteArray byteArray => Char -> P.Parser byteArray byteArray
+takeUntilASCII char = P.takeWhile (\c -> if fromEnum c < 0x80 then fromEnum c /= fromEnum char else True)
+
+tillEOL :: Parser B.ByteString
+tillEOL = P.takeWhile (/= asciiEOL)
+
+skipEOL :: Parser ()
+skipEOL = P.byte asciiEOL >> return ()
+
+skipASCII :: Char -> Parser ()
+skipASCII c
+    | cp < 0x80 = P.byte (fromIntegral cp) >> return ()
+    | otherwise = error ("skipASCII: " ++ show c ++ " not a valid ASCII character")
+  where
+    cp = fromEnum c
+
+asciiEOL :: Word8
+asciiEOL = fromIntegral $ fromEnum '\n'
+
+isByte :: ByteArray byteArray => (Word8 -> Bool) -> P.Parser byteArray Word8
+isByte predicate = do
+    b <- P.anyByte
+    if predicate b then return b else fail "isByte"
+
+takeWhile1 :: (Word8 -> Bool) -> Parser B.ByteString
+takeWhile1 predicate =
+    B.cons <$> isByte predicate <*> P.takeWhile predicate
diff --git a/Data/Git/Path.hs b/Data/Git/Path.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Path.hs
@@ -0,0 +1,54 @@
+-- |
+-- Module      : Data.Git.Path
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Data.Git.Path where
+
+import System.Random
+import Data.Git.Ref
+import Data.Git.Imports
+import Data.Git.OS
+import Data.String
+
+configPath, headsPath, tagsPath, remotesPath, packedRefsPath :: LocalPath -> LocalPath
+configPath gitRepo = gitRepo </> "config"
+
+headsPath gitRepo = gitRepo </> "refs" </> "heads" </> ""
+tagsPath gitRepo  = gitRepo </> "refs" </> "tags" </> ""
+remotesPath gitRepo = gitRepo </> "refs" </> "remotes" </> ""
+packedRefsPath gitRepo = gitRepo </> "packed-refs"
+
+headPath, tagPath, remotePath, specialPath :: LocalPath -> String -> LocalPath
+headPath gitRepo name = headsPath gitRepo </> fromString name
+tagPath gitRepo name = tagsPath gitRepo </> fromString name
+remotePath gitRepo name = remotesPath gitRepo </> fromString name
+specialPath gitRepo name = gitRepo </> fromString name
+
+remoteEntPath :: LocalPath -> String -> String -> LocalPath
+remoteEntPath gitRepo name ent = remotePath gitRepo name </> fromString ent
+
+packDirPath :: LocalPath -> LocalPath
+packDirPath repoPath = repoPath </> "objects" </> "pack"
+
+indexPath, packPath :: LocalPath -> Ref -> LocalPath
+indexPath repoPath indexRef =
+        packDirPath repoPath </> fromString ("pack-" ++ toHexString indexRef ++ ".idx")
+
+packPath repoPath packRef =
+        packDirPath repoPath </> fromString ("pack-" ++ toHexString packRef ++ ".pack")
+
+objectPath :: LocalPath -> String -> String -> LocalPath
+objectPath repoPath d f = repoPath </> "objects" </> fromString d </> fromString f
+
+objectPathOfRef :: LocalPath -> Ref -> LocalPath
+objectPathOfRef repoPath ref = objectPath repoPath d f
+        where (d,f) = toFilePathParts ref
+
+objectTemporaryPath :: LocalPath -> IO LocalPath
+objectTemporaryPath repoPath = do
+        r <- fst . random <$> getStdGen :: IO Int
+        return (repoPath </> "objects" </> fromString ("tmp-" ++ show r ++ ".tmp"))
diff --git a/Data/Git/Ref.hs b/Data/Git/Ref.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Ref.hs
@@ -0,0 +1,125 @@
+-- |
+-- Module      : Data.Git.Ref
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE DeriveDataTypeable #-}
+module Data.Git.Ref
+    ( Ref
+    -- * Exceptions
+    , RefInvalid(..)
+    , RefNotFound(..)
+    -- * convert from bytestring and string
+    , isHex
+    , isHexString
+    , fromHex
+    , fromHexString
+    , fromBinary
+    , fromDigest
+    , toBinary
+    , toHex
+    , toHexString
+    -- * Misc function related to ref
+    , refPrefix
+    , cmpPrefix
+    , toFilePathParts
+    -- * Hash ByteString types to a ref
+    , hash
+    , hashLBS
+    ) where
+
+import qualified Crypto.Hash
+import           Crypto.Hash (Digest, SHA1, digestFromByteString)
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString.Unsafe as B (unsafeIndex)
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Char8 as BC
+import           Data.ByteArray.Encoding
+import qualified Data.ByteArray as B (convert)
+
+import Data.Char (isHexDigit)
+import Data.Data
+
+import Control.Exception (Exception, throw)
+
+-- | represent a git reference (SHA1)
+newtype Ref = Ref (Digest SHA1)
+    deriving (Eq,Ord,Typeable)
+
+instance Show Ref where
+    show = BC.unpack . toHex
+
+-- | Invalid Reference exception raised when
+-- using something that is not a ref as a ref.
+data RefInvalid = RefInvalid ByteString
+    deriving (Show,Eq,Data,Typeable)
+
+-- | Reference wasn't found
+data RefNotFound = RefNotFound Ref
+    deriving (Show,Eq,Typeable)
+
+instance Exception RefInvalid
+instance Exception RefNotFound
+
+isHex :: ByteString -> Bool
+isHex = and . map isHexDigit . BC.unpack
+
+isHexString :: String -> Bool
+isHexString = and . map isHexDigit
+
+-- | take a hexadecimal bytestring that represent a reference
+-- and turn into a ref
+fromHex :: ByteString -> Ref
+fromHex s =
+    case either (const Nothing) Just (convertFromBase Base16 s :: Either String ByteString) >>= digestFromByteString of
+        Nothing  -> throw $ RefInvalid s
+        Just hsh -> Ref hsh
+
+-- | take a hexadecimal string that represent a reference
+-- and turn into a ref
+fromHexString :: String -> Ref
+fromHexString = fromHex . BC.pack
+
+-- | transform a ref into an hexadecimal bytestring
+toHex :: Ref -> ByteString
+toHex (Ref bs) = convertToBase Base16 bs
+
+-- | transform a ref into an hexadecimal string
+toHexString :: Ref -> String
+toHexString (Ref d) = show d
+
+-- | transform a bytestring that represent a binary bytestring
+-- and returns a ref.
+fromBinary :: ByteString -> Ref
+fromBinary b = maybe (throw $ RefInvalid b) Ref $ digestFromByteString b
+
+-- | transform a bytestring that represent a binary bytestring
+-- and returns a ref.
+fromDigest :: Digest SHA1 -> Ref
+fromDigest = Ref
+
+-- | turn a reference into a binary bytestring
+toBinary :: Ref -> ByteString
+toBinary (Ref b) = B.convert b
+
+-- | returns the prefix (leading byte) of this reference
+refPrefix :: Ref -> Int
+refPrefix (Ref b) = fromIntegral $ B.unsafeIndex (B.convert b) 0
+
+-- | compare prefix
+cmpPrefix :: String -> Ref -> Ordering
+cmpPrefix pre ref = pre `compare` (take (length pre) $ toHexString ref)
+
+-- | returns the splitted format "prefix/suffix" for addressing the loose object database
+toFilePathParts :: Ref -> (String, String)
+toFilePathParts ref = splitAt 2 $ show ref
+
+-- | hash a bytestring into a reference
+hash :: ByteString -> Ref
+hash = Ref . Crypto.Hash.hash
+
+-- | hash a lazy bytestring into a reference
+hashLBS :: L.ByteString -> Ref
+hashLBS = Ref . Crypto.Hash.hashlazy
diff --git a/Data/Git/Repository.hs b/Data/Git/Repository.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Repository.hs
@@ -0,0 +1,316 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+-- |
+-- Module      : Data.Git.Repository
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Repository
+    ( Git
+    -- * Config
+    , configGetAll
+    , configGet
+    , Config(..)
+    , Section(..)
+    -- * Trees
+    , HTree
+    , HTreeEnt(..)
+    , RefName(..)
+    , getCommitMaybe
+    , getCommit
+    , getTreeMaybe
+    , getTree
+    , rewrite
+    , buildHTree
+    , resolvePath
+    , resolveTreeish
+    , resolveRevision
+    , initRepo
+    , isRepo
+    -- * named refs manipulation
+    , branchWrite
+    , branchList
+    , tagWrite
+    , tagList
+    , headSet
+    , headGet
+    ) where
+
+import Control.Exception (Exception, throw)
+
+import Data.Maybe (fromMaybe)
+import Data.List (find)
+import Data.Data
+import Data.IORef
+
+import Data.Git.Named
+import Data.Git.Types
+import Data.Git.Imports
+import Data.Git.Storage.Object
+import Data.Git.Storage
+import Data.Git.Revision
+import Data.Git.Storage.Loose
+import Data.Git.Storage.CacheFile
+import Data.Git.Ref
+import Data.Git.Config (Config(..), Section(..))
+import qualified Data.Git.Config as Cfg
+
+import Data.Set (Set)
+
+import qualified Data.Map as M
+import qualified Data.Set as Set
+
+-- | hierarchy tree, either a reference to a blob (file) or a tree (directory).
+data HTreeEnt = TreeDir Ref HTree | TreeFile Ref
+type HTree = [(ModePerm,EntName,HTreeEnt)]
+
+-- | Exception when trying to convert an object pointed by 'Ref' to
+-- a type that is different
+data InvalidType = InvalidType Ref ObjectType
+                 deriving (Show,Eq,Typeable)
+
+instance Exception InvalidType
+
+-- should be a standard function that do that...
+mapJustM :: Monad m => (t -> m (Maybe a)) -> Maybe t -> m (Maybe a)
+mapJustM f (Just o) = f o
+mapJustM _ Nothing  = return Nothing
+
+-- | get a specified commit
+getCommitMaybe :: Git -> Ref -> IO (Maybe Commit)
+getCommitMaybe git ref = maybe Nothing objectToCommit <$> getObject git ref True
+
+-- | get a specified commit but raises an exception if doesn't exists or type is not appropriate
+getCommit :: Git -> Ref -> IO Commit
+getCommit git ref = maybe err id . objectToCommit <$> getObject_ git ref True
+  where err = throw $ InvalidType ref TypeCommit
+
+-- | get a specified tree
+getTreeMaybe :: Git -> Ref -> IO (Maybe Tree)
+getTreeMaybe git ref = maybe Nothing objectToTree <$> getObject git ref True
+
+-- | get a specified tree but raise
+getTree :: Git -> Ref -> IO Tree
+getTree git ref = maybe err id . objectToTree <$> getObject_ git ref True
+  where err = throw $ InvalidType ref TypeTree
+
+-- | try to resolve a string to a specific commit ref
+-- for example: HEAD, HEAD^, master~3, shortRef
+resolveRevision :: Git -> Revision -> IO (Maybe Ref)
+resolveRevision git (Revision prefix modifiers) =
+    getCacheVal (packedNamed git) >>= \c -> resolvePrefix c >>= maybe (return Nothing) (modf modifiers)
+  where
+        resolvePrefix lookupCache = tryResolvers
+              [resolveNamedPrefix lookupCache namedResolvers
+              ,resolvePrePrefix
+              ]
+
+        resolveNamedPrefix _           []     = return Nothing
+        resolveNamedPrefix lookupCache (x:xs) = followToRef (resolveNamedPrefix lookupCache xs) x
+          where followToRef onFailure refty = do
+                    exists <- existsRefFile (gitRepoPath git) refty
+                    if exists
+                        then do refcont <- readRefFile (gitRepoPath git) refty
+                                case refcont of
+                                     RefDirect ref     -> return $ Just ref
+                                     RefLink refspecty -> followToRef onFailure refspecty
+                                     _                 -> error "cannot handle reference content"
+                        else case refty of
+                                RefTag name    -> mapLookup name $ packedTags lookupCache
+                                RefBranch name -> mapLookup name $ packedBranchs lookupCache
+                                RefRemote name -> mapLookup name $ packedRemotes lookupCache
+                                _              -> return Nothing
+                  where mapLookup name m = maybe onFailure (return . Just) $ M.lookup name m
+
+        namedResolvers = case prefix of
+                             "HEAD"       -> [ RefHead ]
+                             "ORIG_HEAD"  -> [ RefOrigHead ]
+                             "FETCH_HEAD" -> [ RefFetchHead ]
+                             _            -> map (flip ($) (RefName prefix)) [RefTag,RefBranch,RefRemote]
+
+        tryResolvers :: [IO (Maybe Ref)] -> IO (Maybe Ref)
+        tryResolvers []            = return $ if (isHexString prefix)
+            then Just $ fromHexString prefix
+            else Nothing
+        tryResolvers (resolver:xs) = resolver >>= isResolved
+           where isResolved (Just r) = return (Just r)
+                 isResolved Nothing  = tryResolvers xs
+
+        resolvePrePrefix :: IO (Maybe Ref)
+        resolvePrePrefix
+            | not (isHexString prefix) = return Nothing
+            | otherwise = do
+                refs <- findReferencesWithPrefix git prefix
+                case refs of
+                    []  -> return Nothing
+                    [r] -> return (Just r)
+                    _   -> error "multiple references with this prefix"
+
+        modf [] ref                  = return (Just ref)
+        modf (RevModParent i:xs) ref = do
+            parentRefs <- getParentRefs ref
+            case i of
+                0 -> error "revision modifier ^0 is not implemented"
+                _ -> case drop (i - 1) parentRefs of
+                          []    -> error "no such parent"
+                          (p:_) -> modf xs p
+
+        modf (RevModParentFirstN 1:xs) ref = modf (RevModParent 1:xs) ref
+        modf (RevModParentFirstN n:xs) ref = do
+            parentRefs <- getParentRefs ref
+            modf (RevModParentFirstN (n-1):xs) (head parentRefs)
+        modf (_:_) _ = error "unimplemented revision modifier"
+
+        getParentRefs ref = commitParents <$> getCommit git ref
+
+-- | returns a tree from a ref that might be either a commit, a tree or a tag.
+resolveTreeish :: Git -> Ref -> IO (Maybe Tree)
+resolveTreeish git ref = getObject git ref True >>= mapJustM recToTree
+  where recToTree (objectToCommit -> Just (Commit { commitTreeish = tree })) = resolveTreeish git tree
+        recToTree (objectToTag    -> Just (Tag tref _ _ _ _))    = resolveTreeish git tref
+        recToTree (objectToTree   -> Just t@(Tree _))            = return $ Just t
+        recToTree _                                              = return Nothing
+
+
+-- | Rewrite a set of commits from a revision and returns the new ref.
+--
+-- If during revision traversal (diving) there's a commit with zero or multiple
+-- parents then the traversal will stop regardless of the amount of parent requested.
+--
+-- calling "rewrite f 2 (revisionOf d)" on the following tree:
+--
+--          a <-- b <-- c <-- d
+--
+-- result in the following tree after mapping with f:
+--
+--          a <-- f(b) <-- f(c) <-- f(d)
+--
+rewrite :: Git                   -- ^ Repository
+        -> (Commit -> IO Commit) -- ^ Mapping function
+        -> Revision              -- ^ revision to start from
+        -> Int                   -- ^ the number of parents to map
+        -> IO Ref                -- ^ return the new head REF
+rewrite git mapCommit revision nbParent = do
+    ref <- fromMaybe (error "revision cannot be found") <$> resolveRevision git revision
+    resolveParents nbParent ref >>= process . reverse
+
+  where resolveParents :: Int -> Ref -> IO [ (Ref, Commit) ]
+        resolveParents 0 ref = (:[]) . (,) ref <$> getCommit git ref
+        resolveParents n ref = do commit <- getCommit git ref
+                                  case commitParents commit of
+                                       [parentRef] -> liftM ((ref,commit) :) (resolveParents (n-1) parentRef)
+                                       _           -> return [(ref,commit)]
+
+        process [] = error "nothing to rewrite"
+        process ((_,commit):next) =
+                    mapCommit commit >>= looseWrite (gitRepoPath git) . toObject >>= flip rewriteOne next
+
+        rewriteOne prevRef [] = return prevRef
+        rewriteOne prevRef ((_,commit):next) = do
+                    newCommit <- mapCommit $ commit { commitParents = [prevRef] }
+                    ref       <- looseWrite (gitRepoPath git) (toObject newCommit)
+                    rewriteOne ref next
+
+-- | build a hierarchy tree from a tree object
+buildHTree :: Git -> Tree -> IO HTree
+buildHTree git (Tree ents) = mapM resolveTree ents
+  where resolveTree (perm, ent, ref) = do
+            obj <- getObjectType git ref
+            case obj of
+                Just TypeBlob -> return (perm, ent, TreeFile ref)
+                Just TypeTree -> do ctree <- getTree git ref
+                                    dir   <- buildHTree git ctree
+                                    return (perm, ent, TreeDir ref dir)
+                Just _        -> error "wrong type embedded in tree object"
+                Nothing       -> error "unknown reference in tree object"
+
+-- | resolve the ref (tree or blob) related to a path at a specific commit ref
+resolvePath :: Git     -- ^ repository
+            -> Ref     -- ^ commit reference
+            -> EntPath -- ^ paths
+            -> IO (Maybe Ref)
+resolvePath git commitRef paths =
+    getCommit git commitRef >>= \commit -> resolve (commitTreeish commit) paths
+  where resolve :: Ref -> EntPath -> IO (Maybe Ref)
+        resolve treeRef []     = return $ Just treeRef
+        resolve treeRef (x:xs) = do
+            (Tree ents) <- getTree git treeRef
+            let cEnt = treeEntRef <$> findEnt x ents
+            if xs == []
+                then return cEnt
+                else maybe (return Nothing) (\z -> resolve z xs) cEnt
+
+        findEnt x = find (\(_, b, _) -> b == x)
+        treeEntRef (_,_,r) = r
+
+-- | Write a branch to point to a specific reference
+branchWrite :: Git     -- ^ repository
+            -> RefName -- ^ the name of the branch to write
+            -> Ref     -- ^ the reference to set
+            -> IO ()
+branchWrite git branchName ref =
+    writeRefFile (gitRepoPath git) (RefBranch branchName) (RefDirect ref)
+
+-- | Return the list of branches
+branchList :: Git -> IO (Set RefName)
+branchList git = do
+    ps <- Set.fromList . M.keys . packedBranchs <$> getCacheVal (packedNamed git)
+    ls <- Set.fromList <$> looseHeadsList (gitRepoPath git)
+    return $ Set.union ps ls
+
+-- | Write a tag to point to a specific reference
+tagWrite :: Git     -- ^ repository
+         -> RefName -- ^ the name of the tag to write
+         -> Ref     -- ^ the reference to set
+         -> IO ()
+tagWrite git tagname ref =
+    writeRefFile (gitRepoPath git) (RefTag tagname) (RefDirect ref)
+
+-- | Return the list of branches
+tagList :: Git -> IO (Set RefName)
+tagList git = do
+    ps <- Set.fromList . M.keys . packedTags <$> getCacheVal (packedNamed git)
+    ls <- Set.fromList <$> looseTagsList (gitRepoPath git)
+    return $ Set.union ps ls
+
+-- | Set head to point to either a reference or a branch name.
+headSet :: Git                -- ^ repository
+        -> Either Ref RefName -- ^ either a raw reference or a branch name
+        -> IO ()
+headSet git (Left ref)      =
+    writeRefFile (gitRepoPath git) RefHead (RefDirect ref)
+headSet git (Right refname) =
+    writeRefFile (gitRepoPath git) RefHead (RefLink $ RefBranch refname)
+
+-- | Get what the head is pointing to, or the reference otherwise
+headGet :: Git
+        -> IO (Either Ref RefName)
+headGet git = do
+    content <- readRefFile (gitRepoPath git) RefHead
+    case content of
+        RefLink (RefBranch b) -> return $ Right b
+        RefLink spec          -> error ("unknown content link in HEAD: " ++ show spec)
+        RefDirect r           -> return $ Left r
+        RefContentUnknown bs  -> error ("unknown content in HEAD: " ++ show bs)
+
+-- | Read the Config
+configGetAll :: Git -> IO [Config]
+configGetAll git = readIORef (configs git)
+
+-- | Get a configuration element from the config file, starting from the
+-- local repository config file, then the global config file.
+--
+-- for example the equivalent to git config user.name is:
+--
+-- > configGet git "user" "name"
+--
+configGet :: Git               -- ^ Git context
+          -> String            -- ^ section name
+          -> String            -- ^ key name
+          -> IO (Maybe String) -- ^ The resulting value if it exists
+configGet git section key = do
+    cfgs <- configGetAll git
+    return $ Cfg.get cfgs section key
diff --git a/Data/Git/Revision.hs b/Data/Git/Revision.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Revision.hs
@@ -0,0 +1,129 @@
+-- |
+-- Module      : Data.Git.Revision
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE DeriveDataTypeable #-}
+module Data.Git.Revision
+    ( Revision(..)
+    , RevModifier(..)
+    , RevisionNotFound(..)
+    , fromString
+    ) where
+
+import Control.Applicative
+import Control.Arrow (first)
+import Data.String
+import Data.List
+import Data.Data
+import Data.Char
+
+-- | A modifier to a revision, which is
+-- a function apply of a revision
+data RevModifier =
+      RevModParent Int       -- ^ parent accessor ^<n> and ^
+    | RevModParentFirstN Int -- ^ parent accessor ~<n>
+    | RevModAtType String    -- ^ @{type} accessor
+    | RevModAtDate String    -- ^ @{date} accessor
+    | RevModAtN Int          -- ^ @{n} accessor
+    deriving (Eq,Data,Typeable)
+
+instance Show RevModifier where
+    show (RevModParent 1)       = "^"
+    show (RevModParent n)       = "^" ++ show n
+    show (RevModParentFirstN n) = "~" ++ show n
+    show (RevModAtType s)       = "@{" ++ s ++ "}"
+    show (RevModAtDate s)       = "@{" ++ s ++ "}"
+    show (RevModAtN s)          = "@{" ++ show s ++ "}"
+
+-- | A git revision. this can be many things:
+--    * a shorten ref
+--    * a ref
+--    * a named branch or tag
+--  followed by optional modifiers 'RevModifier' that can represent:
+--    * parenting
+--    * type
+--    * date
+data Revision = Revision String [RevModifier]
+    deriving (Eq,Data,Typeable)
+
+-- | Exception when a revision cannot be resolved to a reference
+data RevisionNotFound = RevisionNotFound Revision
+    deriving (Show,Eq,Data,Typeable)
+
+instance Show Revision where
+    show (Revision s ms) = s ++ concatMap show ms
+
+instance IsString Revision where
+    fromString = revFromString
+
+revFromString :: String -> Revision
+revFromString s = either (error.show) fst $ runStream parser s
+  where parser :: Stream Char Revision
+        parser = do
+            p    <- many (noneOf "^~@")
+            mods <- many (parseParent <|> parseFirstParent <|> parseAt)
+            return $ Revision p mods
+        parseParent = do
+            _ <- char '^'
+            n <- optional (some digit)
+            case n of
+                Nothing -> return $ RevModParent 1
+                Just d  -> return $ RevModParent (read d)
+        parseFirstParent =
+            RevModParentFirstN . read <$> (char '~' *> some digit)
+        parseAt = do
+            _  <- char '@' >> char '{'
+            at <- parseAtType <|> parseAtDate <|> parseAtN
+            _  <- char '}'
+            return at
+        parseAtType = do
+            RevModAtType <$> (string "tree" <|> string "commit" <|> string "blob" <|> string "tag")
+        parseAtN = do
+            RevModAtN . read <$> some digit
+        parseAtDate = do
+            RevModAtDate <$> many (noneOf "}")
+
+-- combinator
+
+        char c = eatRet (\x -> if x == c then Just c else Nothing)
+        string str = prefix (\x -> if isPrefixOf str x then Just (str, length str) else Nothing)
+        digit = eatRet (\x -> if isDigit x then Just x else Nothing)
+        noneOf l = eatRet (\x -> if not (x `elem` l) then Just x else Nothing)
+
+prefix :: ([elem] -> Maybe (a, Int)) -> Stream elem a
+prefix predicate = Stream $ \el ->
+    case el of
+        [] -> Left ("empty stream: prefix")
+        _  ->
+            case predicate el of
+                Just (a,i) -> Right (a, drop i el)
+                Nothing    -> Left ("unexpected stream")
+
+eatRet :: Show elem => (elem -> Maybe a) -> Stream elem a
+eatRet predicate = Stream $ \el ->
+    case el of
+        []   -> Left ("empty stream: eating")
+        x:xs ->
+            case predicate x of
+                Just a  -> Right (a, xs)
+                Nothing -> Left ("unexpected atom got: " ++ show x)
+
+newtype Stream elem a = Stream { runStream :: [elem] -> Either String (a, [elem]) }
+instance Functor (Stream elem) where
+    fmap f s = Stream $ \e1 -> case runStream s e1 of
+        Left err     -> Left err
+        Right (a,e2) -> Right (f a, e2)
+instance Applicative (Stream elem) where
+    pure  = return
+    fab <*> fa = Stream $ \e1 -> case runStream fab e1 of
+        Left err      -> Left err
+        Right (f, e2) -> either Left (Right . first f) $ runStream fa e2
+instance Alternative (Stream elem) where
+    empty     = Stream $ \_  -> Left "empty"
+    f1 <|> f2 = Stream $ \e1 -> either (\_ -> runStream f2 e1) Right $ runStream f1 e1
+instance Monad (Stream elem) where
+    return a  = Stream $ \e1 -> Right (a, e1)
+    ma >>= mb = Stream $ \e1 -> either Left (\(a, e2) -> runStream (mb a) e2) $ runStream ma e1
diff --git a/Data/Git/Storage.hs b/Data/Git/Storage.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage.hs
@@ -0,0 +1,349 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- Module      : Data.Git.Storage
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+
+module Data.Git.Storage
+    ( Git
+    , packedNamed
+    , gitRepoPath
+    , configs
+    -- * opening repositories
+    , openRepo
+    , closeRepo
+    , withRepo
+    , withCurrentRepo
+    , findRepoMaybe
+    , findRepo
+    , isRepo
+    -- * creating repositories
+    , initRepo
+    -- * repository accessors
+    , getDescription
+    , setDescription
+    -- * iterators
+    , iterateIndexes
+    , findReference
+    , findReferencesWithPrefix
+    -- * getting objects
+    , getObjectRaw
+    , getObjectRawAt
+    , getObject
+    , getObject_
+    , getObjectAt
+    , getObjectType
+    -- * setting objects
+    , setObject
+    ) where
+
+import Control.Exception
+import qualified Control.Exception as E
+import Control.Monad
+
+import Data.List ((\\), isPrefixOf)
+import Data.Either (partitionEithers)
+import Data.IORef
+import Data.Word
+
+import Data.Git.Named
+import Data.Git.Imports
+import Data.Git.OS
+import Data.Git.Path (packedRefsPath)
+import Data.Git.Delta
+import Data.Git.Storage.FileReader
+import Data.Git.Storage.PackIndex
+import Data.Git.Storage.Object
+import Data.Git.Storage.Pack
+import Data.Git.Storage.Loose
+import Data.Git.Storage.CacheFile
+import Data.Git.Ref
+import Data.Git.Config
+import qualified Data.ByteString.Lazy as L
+
+import qualified Data.Map as M
+
+data PackIndexReader = PackIndexReader PackIndexHeader FileReader
+
+-- | this is a cache representation of the packed-ref file
+type CachedPackedRef = CacheFile (PackedRefs (M.Map RefName Ref))
+
+-- | represent a git repo, with possibly already opened filereaders
+-- for indexes and packs
+data Git = Git
+    { gitRepoPath  :: LocalPath
+    , indexReaders :: IORef [(Ref, PackIndexReader)]
+    , packReaders  :: IORef [(Ref, FileReader)]
+    , packedNamed  :: CachedPackedRef
+    , configs      :: IORef [Config]
+    }
+
+-- | open a new git repository context
+openRepo :: LocalPath -> IO Git
+openRepo path = Git path <$> newIORef []
+                         <*> newIORef []
+                         <*> packedRef
+                         <*> (readConfigs >>= newIORef)
+  where packedRef = newCacheVal (packedRefsPath path)
+                                (readPackedRefs path M.fromList)
+                                (PackedRefs M.empty M.empty M.empty)
+        readConfigs = do
+            global <- E.try readGlobalConfig :: IO (Either IOException Config)
+            local  <- E.try (readConfig path)
+            return $ snd $ partitionEithers [local,global]
+
+-- | close a git repository context, closing all remaining fileReaders.
+closeRepo :: Git -> IO ()
+closeRepo (Git { indexReaders = ireaders, packReaders = preaders }) = do
+    mapM_ (closeIndexReader . snd) =<< readIORef ireaders
+    mapM_ (fileReaderClose . snd) =<< readIORef preaders
+  where closeIndexReader (PackIndexReader _ fr) = fileReaderClose fr
+
+-- | Find the git repository from the current directory.
+--
+-- If the environment variable GIT_DIR is set then it's used,
+-- otherwise iterate from current directory, up to 128 parents for a .git directory
+findRepoMaybe :: IO (Maybe LocalPath)
+findRepoMaybe = do
+    menvDir <- E.catch (Just <$> getEnvAsPath "GIT_DIR") (\(_:: SomeException) -> return Nothing)
+    case menvDir of
+        Nothing     -> getWorkingDirectory >>= checkDir 0
+        Just envDir -> isRepo envDir >>= \e -> return (if e then Just envDir else Nothing)
+  where checkDir :: Int -> LocalPath -> IO (Maybe LocalPath)
+        checkDir 128 _  = return Nothing
+        checkDir n   wd = do
+            let filepath = wd </> ".git"
+            e <- isRepo filepath
+            if e then return (Just filepath) else checkDir (n+1) (if absolute wd then parent wd else wd </> "..")
+
+-- | Find the git repository from the current directory.
+--
+-- If the environment variable GIT_DIR is set then it's used,
+-- otherwise iterate from current directory, up to 128 parents for a .git directory
+findRepo :: IO LocalPath
+findRepo = do
+    menvDir <- E.catch (Just <$> getEnvAsPath "GIT_DIR") (\(_:: SomeException) -> return Nothing)
+    case menvDir of
+        Nothing     -> getWorkingDirectory >>= checkDir 0
+        Just envDir -> do
+            e <- isRepo envDir
+            when (not e) $ error "environment GIT_DIR is not a git repository"
+            return envDir
+  where checkDir :: Int -> LocalPath -> IO LocalPath
+        checkDir 128 _  = error "not a git repository"
+        checkDir n   wd = do
+            let filepath = wd </> ".git"
+            e <- isRepo filepath
+            if e then return filepath else checkDir (n+1) (if absolute wd then parent wd else wd </> "..")
+
+-- | execute a function f with a git context.
+withRepo :: LocalPath -> (Git -> IO c) -> IO c
+withRepo path f = bracket (openRepo path) closeRepo f
+
+-- | execute a function on the current repository.
+--
+-- check findRepo to see how the git repository is found.
+withCurrentRepo :: (Git -> IO a) -> IO a
+withCurrentRepo f = findRepo >>= \path -> withRepo path f
+
+-- | basic checks to see if a specific path looks like a git repo.
+isRepo :: LocalPath -> IO Bool
+isRepo path = do
+    dir     <- isDirectory path
+    subDirs <- mapM (isDirectory . (path </>))
+                    [ "hooks", "info"
+                    , "objects", "refs"
+                    , "refs"</> "heads", "refs"</> "tags"]
+    return $ and ([dir] ++ subDirs)
+
+-- | initialize a new repository at a specific location.
+initRepo :: LocalPath -> IO ()
+initRepo path = do
+    exists <- isDirectory path
+    when exists $ error "destination directory already exists"
+    createParentDirectory path
+    mapM_ (createDirectory False . (path </>))
+        [ "branches", "hooks", "info"
+        , "logs", "objects", "refs"
+        , "refs"</> "heads", "refs"</> "tags"]
+
+-- | read the repository's description
+getDescription :: Git -> IO (Maybe String)
+getDescription git = do
+    isdescription <- isFile descriptionPath
+    if (isdescription)
+        then do
+                content <- readTextFile descriptionPath
+                return $ Just content
+        else return Nothing
+  where descriptionPath = (gitRepoPath git) </> "description"
+
+-- | set the repository's description
+setDescription :: Git -> String -> IO ()
+setDescription git desc = do
+    writeTextFile descriptionPath desc
+  where descriptionPath = (gitRepoPath git) </> "description"
+
+iterateIndexes :: Git
+               -> (b -> (Ref, PackIndexReader) -> IO (b, Bool))
+               -> b -> IO b
+iterateIndexes git f initAcc = do
+    allIndexes    <- packIndexEnumerate (gitRepoPath git)
+    readers       <- readIORef (indexReaders git)
+    (a,terminate) <- loop initAcc readers
+    if terminate
+        then return a
+        else readRemainingIndexes a (allIndexes \\ map fst readers)
+  where loop acc []     = return (acc, False)
+        loop acc (r:rs) = do
+            (nacc, terminate) <- f acc r
+            if terminate
+                then return (nacc,True)
+                else loop nacc rs
+
+        readRemainingIndexes acc []            = return acc
+        readRemainingIndexes acc (idxref:idxs) = do
+            fr <- packIndexOpen (gitRepoPath git) idxref
+            idx <- packIndexReadHeader fr
+            let idxreader = PackIndexReader idx fr
+            let r = (idxref, idxreader)
+            modifyIORef (indexReaders git) (\l -> r : l)
+            (nacc, terminate) <- f acc r
+            if terminate
+                then return nacc
+                else readRemainingIndexes nacc idxs
+
+-- | Get the object location of a specific reference
+findReference :: Git -> Ref -> IO ObjectLocation
+findReference git ref = maybe NotFound id <$> (findLoose `mplusIO` findInIndexes)
+  where findLoose :: IO (Maybe ObjectLocation)
+        findLoose = do
+            isLoose <- looseExists (gitRepoPath git) ref
+            if isLoose then return (Just $ Loose ref) else return Nothing
+
+        findInIndexes :: IO (Maybe ObjectLocation)
+        findInIndexes = iterateIndexes git isinIndex Nothing --f -> (a -> IndexReader -> IO (a,Bool)) -> a -> IO a
+
+        isinIndex acc (idxref, (PackIndexReader idxhdr indexreader)) = do
+            mloc <- packIndexGetReferenceLocation idxhdr indexreader ref
+            case mloc of
+                Nothing  -> return (acc, False)
+                Just loc -> return (Just $ Packed idxref loc, True)
+
+        mplusIO :: IO (Maybe a) -> IO (Maybe a) -> IO (Maybe a)
+        mplusIO f g = f >>= \vopt -> case vopt of
+            Nothing -> g
+            Just v  -> return $ Just v
+
+-- | get all the references that start by a specific prefix
+findReferencesWithPrefix :: Git -> String -> IO [Ref]
+findReferencesWithPrefix git pre
+    | invalidLength         = error ("not a valid prefix: " ++ show pre)
+    | not (isHexString pre) = error ("reference prefix contains non hexchar: " ++ show pre)
+    | otherwise             = do
+        looseRefs  <- looseEnumerateWithPrefixFilter (gitRepoPath git) (take 2 pre) matchRef
+        packedRefs <- concat <$> iterateIndexes git idxPrefixMatch []
+        return (looseRefs ++ packedRefs)
+  where -- not very efficient way to do that... will do for now.
+        matchRef ref = pre `isPrefixOf` toHexString ref
+        invalidLength = length pre < 2 || length pre > 39
+
+        idxPrefixMatch acc (_, (PackIndexReader idxhdr indexreader)) = do
+            refs <- packIndexGetReferencesWithPrefix idxhdr indexreader pre
+            return (refs:acc,False)
+
+readRawFromPack :: Git -> Ref -> Word64 -> IO (FileReader, PackedObjectRaw)
+readRawFromPack git pref offset = do
+    readers <- readIORef (packReaders git)
+    reader  <- maybe getDefault return $ lookup pref readers
+    po <- packReadRawAtOffset reader offset
+    return (reader, po)
+  where getDefault = do p <- packOpen (gitRepoPath git) pref
+                        modifyIORef (packReaders git) ((pref, p):)
+                        return p
+
+readFromPack :: Git -> Ref -> Word64 -> Bool -> IO (Maybe ObjectInfo)
+readFromPack git pref o resolveDelta = do
+    (reader, x) <- readRawFromPack git pref o
+    if resolveDelta then resolve reader o x else return $ Just $ generifyHeader x
+  where generifyHeader :: PackedObjectRaw -> ObjectInfo
+        generifyHeader (po, objData) = ObjectInfo { oiHeader = hdr, oiData = objData, oiChains = [] }
+          where hdr = (poiType po, poiActualSize po, poiExtra po)
+
+        resolve :: FileReader -> Word64 -> PackedObjectRaw -> IO (Maybe ObjectInfo)
+        resolve reader offset (po, objData) = do
+            case (poiType po, poiExtra po) of
+                (TypeDeltaOff, Just ptr@(PtrOfs doff)) -> do
+                    let delta = deltaRead (L.toChunks objData)
+                    let noffset = offset - doff
+                    base <- resolve reader noffset =<< packReadRawAtOffset reader noffset
+                    return $ addToChain ptr $ applyDelta delta base
+                (TypeDeltaRef, Just ptr@(PtrRef bref)) -> do
+                    let delta = deltaRead (L.toChunks objData)
+                    base <- getObjectRaw git bref True
+                    return $ addToChain ptr $ applyDelta delta base
+                _ ->
+                    return $ Just $ generifyHeader (po, objData)
+
+        addToChain ptr (Just oi) = Just (oi { oiChains = ptr : oiChains oi })
+        addToChain _   Nothing   = Nothing
+
+        applyDelta :: Maybe Delta -> Maybe ObjectInfo -> Maybe ObjectInfo
+        applyDelta (Just delta@(Delta _ rSize _)) (Just objInfo) = Just $ objInfo
+            { oiHeader = (\(a,_,c) -> (a,rSize,c)) $ oiHeader objInfo
+            , oiData   = deltaApply (oiData objInfo) delta
+            }
+        applyDelta _ _                                      = Nothing
+
+-- | get an object from repository
+getObjectRawAt :: Git -> ObjectLocation -> Bool -> IO (Maybe ObjectInfo)
+getObjectRawAt _   NotFound    _ = return Nothing
+getObjectRawAt git (Loose ref) _ = Just . (\(h,d)-> ObjectInfo h d[]) <$> looseReadRaw (gitRepoPath git) ref
+getObjectRawAt git (Packed pref o) resolveDelta = readFromPack git pref o resolveDelta
+
+-- | get an object from repository
+getObjectRaw :: Git -> Ref -> Bool -> IO (Maybe ObjectInfo)
+getObjectRaw git ref resolveDelta = do
+    loc <- findReference git ref
+    getObjectRawAt git loc resolveDelta
+
+-- | get an object type from repository
+getObjectType :: Git -> Ref -> IO (Maybe ObjectType)
+getObjectType git ref = findReference git ref >>= getObjectTypeAt
+  where getObjectTypeAt NotFound        = return Nothing
+        getObjectTypeAt (Loose _)       = Just . (\(t,_,_) -> t) <$> looseReadHeader (gitRepoPath git) ref
+        getObjectTypeAt (Packed pref o) =
+            fmap ((\(ty,_,_) -> ty) . oiHeader) <$> readFromPack git pref o True
+
+-- | get an object from repository using a location to reference it.
+getObjectAt :: Git -> ObjectLocation -> Bool -> IO (Maybe Object)
+getObjectAt git loc resolveDelta = maybe Nothing toObj <$> getObjectRawAt git loc resolveDelta
+  where toObj (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)
+
+-- | get an object from repository using a ref.
+getObject :: Git               -- ^ repository
+          -> Ref               -- ^ the object's reference to
+          -> Bool              -- ^ whether to resolve deltas if found
+          -> IO (Maybe Object) -- ^ returned object if found
+getObject git ref resolveDelta = maybe Nothing toObj <$> getObjectRaw git ref resolveDelta
+  where toObj (ObjectInfo { oiHeader = (ty, _, extra), oiData = objData }) = packObjectFromRaw (ty, extra, objData)
+
+-- | Just like 'getObject' but will raise a RefNotFound exception if the
+-- reference cannot be found.
+getObject_ :: Git       -- ^ repository
+           -> Ref       -- ^ the object's reference to
+           -> Bool      -- ^ whether to resolve deltas if found
+           -> IO Object -- ^ returned object if found
+getObject_ git ref resolveDelta = maybe (throwIO $ RefNotFound ref) return
+                              =<< getObject git ref resolveDelta
+
+-- | set an object in the store and returns the new ref
+-- this is always going to create a loose object.
+setObject :: Git
+          -> Object
+          -> IO Ref
+setObject git obj = looseWrite (gitRepoPath git) obj
diff --git a/Data/Git/Storage/CacheFile.hs b/Data/Git/Storage/CacheFile.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/CacheFile.hs
@@ -0,0 +1,37 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+-- |
+-- Module      : Data.Git.Storage.CacheFile
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Storage.CacheFile (CacheFile, newCacheVal, getCacheVal) where
+
+import Control.Concurrent.MVar
+import qualified Control.Exception as E
+import           Data.Git.Imports
+import           Data.Git.OS
+
+data CacheFile a = CacheFile
+    { cacheFilepath :: LocalPath
+    , cacheRefresh  :: IO a
+    , cacheIniVal   :: a
+    , cacheLock     :: MVar (MTime, a)
+    }
+
+newCacheVal :: LocalPath -> IO a -> a -> IO (CacheFile a)
+newCacheVal path refresh initialVal =
+    CacheFile path refresh initialVal <$> newMVar (MTime timeZero, initialVal)
+
+getCacheVal :: CacheFile a -> IO a
+getCacheVal cachefile = modifyMVar (cacheLock cachefile) getOrRefresh
+    where getOrRefresh s@(mtime, cachedVal) = do
+             cMTime <- tryGetMTime $ cacheFilepath cachefile
+             case cMTime of
+                  Nothing -> return ((MTime timeZero, cacheIniVal cachefile), cacheIniVal cachefile)
+                  Just newMtime | newMtime > mtime -> cacheRefresh cachefile >>= \v -> return ((newMtime, v), v)
+                                | otherwise        -> return (s, cachedVal)
+
+tryGetMTime :: LocalPath -> IO (Maybe MTime)
+tryGetMTime filepath = (Just <$> getMTime filepath) `E.catch` \(_ :: E.SomeException) -> return Nothing
diff --git a/Data/Git/Storage/FileReader.hs b/Data/Git/Storage/FileReader.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/FileReader.hs
@@ -0,0 +1,206 @@
+-- |
+-- Module      : Data.Git.Storage.FileReader
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE DeriveDataTypeable #-}
+module Data.Git.Storage.FileReader
+        ( FileReader
+        , fileReaderNew
+        , fileReaderClose
+        , withFileReader
+        , withFileReaderDecompress
+        , fileReaderGetPos
+        , fileReaderGet
+        , fileReaderGetLBS
+        , fileReaderGetBS
+        , fileReaderGetVLF
+        , fileReaderSeek
+        , fileReaderParse
+
+        , fileReaderInflateToSize
+        ) where
+
+
+import Control.Exception (bracket, throwIO)
+
+import Data.ByteString (ByteString)
+import Data.ByteString.Unsafe
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+import Data.ByteString.Lazy.Internal (defaultChunkSize)
+import Data.IORef
+
+import Data.Git.Imports
+import Data.Git.OS
+import qualified Data.Git.Parser as P
+
+import Data.Data
+import Data.Word
+
+import Codec.Zlib
+import Codec.Zlib.Lowlevel
+import Foreign.ForeignPtr
+import qualified Control.Exception as E
+
+import System.IO (hSeek, SeekMode(..))
+
+data FileReader = FileReader
+        { fbHandle     :: Handle
+        , fbUseInflate :: Bool
+        , fbInflate    :: Inflate
+        , fbRemaining  :: IORef (Maybe ByteString)
+        , fbPos        :: IORef Word64
+        }
+
+data InflateException = InflateException Word64 Word64 String
+    deriving (Show,Eq,Typeable)
+
+instance E.Exception InflateException
+
+fileReaderNew :: Bool -> Handle -> IO FileReader
+fileReaderNew decompress handle = do
+        ref <- newIORef (Just B.empty)
+        pos <- newIORef 0
+        inflate <- initInflate defaultWindowBits
+        return $ FileReader handle decompress inflate ref pos
+
+fileReaderClose :: FileReader -> IO ()
+fileReaderClose = hClose . fbHandle
+
+withFileReader :: LocalPath -> (FileReader -> IO a) -> IO a
+withFileReader path f =
+        bracket (openFile path ReadMode) (hClose) $ \handle ->
+                bracket (fileReaderNew False handle) (\_ -> return ()) f
+
+withFileReaderDecompress :: LocalPath -> (FileReader -> IO a) -> IO a
+withFileReaderDecompress path f =
+        bracket (openFile path ReadMode) (hClose) $ \handle ->
+                bracket (fileReaderNew True handle) (\_ -> return ()) f
+
+fileReaderGetNext :: FileReader -> IO (Maybe ByteString)
+fileReaderGetNext fb = do
+    bs <- if fbUseInflate fb then inflateTillPop else B.hGet (fbHandle fb) 8192
+    modifyIORef (fbPos fb) (\pos -> pos + (fromIntegral $ B.length bs))
+    return $ nothingOnNull bs
+  where
+    inflateTillPop = do
+        b <- B.hGet (fbHandle fb) 4096
+        if B.null b
+            then finishInflate (fbInflate fb)
+            else (>>= maybe inflateTillPop return) =<< feedInflate (fbInflate fb) b
+    nothingOnNull b
+        | B.null b  = Nothing
+        | otherwise = Just b
+
+fileReaderGetPos :: FileReader -> IO Word64
+fileReaderGetPos fr = do
+    storeLeft <- maybe 0 B.length <$> readIORef (fbRemaining fr)
+    pos       <- readIORef (fbPos fr)
+    return (pos - fromIntegral storeLeft)
+
+fileReaderFill :: FileReader -> IO ()
+fileReaderFill fb = fileReaderGetNext fb >>= writeIORef (fbRemaining fb)
+
+fileReaderGet :: Int -> FileReader -> IO [ByteString]
+fileReaderGet size fb@(FileReader { fbRemaining = ref }) = loop size
+  where
+    loop left = do
+        b <- maybe B.empty id <$> readIORef ref
+        if B.length b >= left
+            then do
+                let (b1, b2) = B.splitAt left b
+                writeIORef ref (Just b2)
+                return [b1]
+            else do
+                let nleft = left - B.length b
+                fileReaderFill fb
+                liftM (b :) (loop nleft)
+
+fileReaderGetLBS :: Int -> FileReader -> IO L.ByteString
+fileReaderGetLBS size fb = L.fromChunks <$> fileReaderGet size fb
+
+fileReaderGetBS :: Int -> FileReader -> IO ByteString
+fileReaderGetBS size fb = B.concat <$> fileReaderGet size fb
+
+-- | seek in a handle, and reset the remaining buffer to empty.
+fileReaderSeek :: FileReader -> Word64 -> IO ()
+fileReaderSeek (FileReader { fbHandle = handle, fbRemaining = ref, fbPos = pos }) absPos = do
+        writeIORef ref (Just B.empty) >> writeIORef pos absPos >> hSeek handle AbsoluteSeek (fromIntegral absPos)
+
+-- | parse from a filebuffer
+fileReaderParse :: FileReader -> P.Parser a -> IO a
+fileReaderParse fr@(FileReader { fbRemaining = ref }) parseF = do
+    initBS <- maybe B.empty id <$> readIORef ref
+    result <- P.parseFeed (fileReaderGetNext fr) parseF initBS
+    case result of
+        P.ParseOK remaining a -> writeIORef ref (Just remaining) >> return a
+        P.ParseMore _         -> error "parsing failed: partial with a handle, reached EOF ?"
+        P.ParseFail err       -> error ("parsing failed: " ++ err)
+
+-- | get a Variable Length Field. get byte as long as MSB is set, and one byte after
+fileReaderGetVLF :: FileReader -> IO [Word8]
+fileReaderGetVLF fr = fileReaderParse fr P.vlf
+
+fileReaderInflateToSize :: FileReader -> Word64 -> IO L.ByteString
+fileReaderInflateToSize fb@(FileReader { fbRemaining = ref }) outputSize = do
+        --pos <- fileReaderGetPos fb
+        --putStrLn ("inflate to size " ++ show outputSize ++ " " ++ show pos)
+        inflate <- inflateNew
+        l       <- loop inflate outputSize
+        --posend <- fileReaderGetPos fb
+        --putStrLn ("inflated input " ++ show posend)
+        return $ L.fromChunks l
+        where loop inflate left = do
+                rbs <- readIORef ref
+                let maxToInflate = min left (16 * 1024)
+                let lastBlock = if left == maxToInflate then True else False
+                (dbs,remaining) <- inflateToSize inflate (fromIntegral maxToInflate) lastBlock (maybe B.empty id rbs) (maybe B.empty id <$> fileReaderGetNext fb)
+                                   `E.catch` augmentAndRaise left
+                writeIORef ref (Just remaining)
+                let nleft = left - fromIntegral (B.length dbs)
+                if nleft > 0
+                        then liftM (dbs:) (loop inflate nleft)
+                        else return [dbs]
+              augmentAndRaise :: Word64 -> E.SomeException -> IO a
+              augmentAndRaise left exn = throwIO $ InflateException outputSize left (show exn)
+
+-- lowlevel helpers to inflate only to a specific size.
+inflateNew :: IO (ForeignPtr ZStreamStruct)
+inflateNew = do
+        zstr <- zstreamNew
+        inflateInit2 zstr defaultWindowBits
+        newForeignPtr c_free_z_stream_inflate zstr
+
+inflateToSize :: ForeignPtr ZStreamStruct -> Int -> Bool -> ByteString -> IO ByteString -> IO (ByteString, ByteString)
+inflateToSize inflate sz isLastBlock ibs nextBs = withForeignPtr inflate $ \zstr -> do
+        let boundSz = min defaultChunkSize sz
+        -- create an output buffer
+        fbuff <- mallocForeignPtrBytes boundSz
+        withForeignPtr fbuff $ \buff -> do
+                c_set_avail_out zstr buff (fromIntegral boundSz)
+                rbs <- loop zstr ibs
+                bs  <- B.packCStringLen (buff, boundSz)
+                return (bs, rbs)
+        where
+                loop zstr nbs = do
+                        (ai, streamEnd) <- inflateOneInput zstr nbs
+                        ao <- c_get_avail_out zstr
+                        if (isLastBlock && streamEnd) || (not isLastBlock && ao == 0)
+                                then return $ bsTakeLast ai nbs
+                                else do
+                                        --when (ai /= 0) $ error ("input not consumed completly: ai" ++ show ai)
+                                        (if ai == 0
+                                                then nextBs
+                                                else return (bsTakeLast ai nbs)) >>= loop zstr
+
+                inflateOneInput zstr bs = unsafeUseAsCStringLen bs $ \(istr, ilen) -> do
+                        c_set_avail_in zstr istr $ fromIntegral ilen
+                        r <- c_call_inflate_noflush zstr
+                        when (r < 0 && r /= (-5)) $ do
+                                throwIO $ ZlibException $ fromIntegral r
+                        ai <- c_get_avail_in zstr
+                        return (ai, r == 1)
+                bsTakeLast len bs = B.drop (B.length bs - fromIntegral len) bs
diff --git a/Data/Git/Storage/FileWriter.hs b/Data/Git/Storage/FileWriter.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/FileWriter.hs
@@ -0,0 +1,64 @@
+-- |
+-- Module      : Data.Git.Storage.FileWriter
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Storage.FileWriter where
+
+import Data.Git.Ref
+import Data.Git.OS
+import Data.IORef
+import qualified Data.ByteString as B
+import Codec.Zlib
+import Control.Exception (bracket)
+
+import Crypto.Hash
+
+defaultCompression :: Int
+defaultCompression = 6
+
+-- this is a copy of modifyIORef' found in base 4.6 (ghc 7.6),
+-- for older version of base.
+modifyIORefStrict :: IORef a -> (a -> a) -> IO ()
+modifyIORefStrict ref f = do
+    x <- readIORef ref
+    let x' = f x
+    x' `seq` writeIORef ref x'
+
+data FileWriter = FileWriter
+        { writerHandle  :: Handle
+        , writerDeflate :: Deflate
+        , writerDigest  :: IORef (Context SHA1)
+        }
+
+fileWriterNew :: Handle -> IO FileWriter
+fileWriterNew handle = do
+        deflate <- initDeflate defaultCompression defaultWindowBits
+        digest  <- newIORef hashInit
+        return $ FileWriter
+                { writerHandle  = handle
+                , writerDeflate = deflate
+                , writerDigest  = digest
+                }
+
+withFileWriter :: LocalPath -> (FileWriter -> IO c) -> IO c
+withFileWriter path f =
+        bracket (openFile path WriteMode) (hClose) $ \handle ->
+                bracket (fileWriterNew handle) (fileWriterClose) f
+
+postDeflate :: Handle -> Maybe B.ByteString -> IO ()
+postDeflate handle = maybe (return ()) (B.hPut handle)
+
+fileWriterOutput :: FileWriter -> B.ByteString -> IO ()
+fileWriterOutput (FileWriter { writerHandle = handle, writerDigest = digest, writerDeflate = deflate }) bs = do
+        modifyIORefStrict digest (\ctx -> hashUpdate ctx bs)
+        (>>= postDeflate handle) =<< feedDeflate deflate bs
+
+fileWriterClose :: FileWriter -> IO ()
+fileWriterClose (FileWriter { writerHandle = handle, writerDeflate = deflate }) =
+        postDeflate handle =<< finishDeflate deflate
+
+fileWriterGetDigest :: FileWriter -> IO Ref
+fileWriterGetDigest (FileWriter { writerDigest = digest }) = (fromDigest . hashFinalize) `fmap` readIORef digest
diff --git a/Data/Git/Storage/Loose.hs b/Data/Git/Storage/Loose.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/Loose.hs
@@ -0,0 +1,199 @@
+-- |
+-- Module      : Data.Git.Storage.Loose
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, ViewPatterns #-}
+module Data.Git.Storage.Loose
+        (
+          Zipped(..)
+        -- * marshall from and to lazy bytestring
+        , looseUnmarshall
+        , looseUnmarshallRaw
+        , looseUnmarshallZipped
+        , looseUnmarshallZippedRaw
+        , looseMarshall
+        -- * read and check object existence
+        , looseRead
+        , looseReadHeader
+        , looseReadRaw
+        , looseExists
+        -- * write objects
+        , looseWriteBlobFromFile
+        , looseWrite
+        -- * enumeration of loose objects
+        , looseEnumeratePrefixes
+        , looseEnumerateWithPrefixFilter
+        , looseEnumerateWithPrefix
+        ) where
+
+import Codec.Compression.Zlib
+import Data.Git.Ref
+import Data.Git.Path
+import Data.Git.Internal
+import Data.Git.OS
+import Data.Git.Imports
+import Data.Git.Storage.FileWriter
+import Data.Git.Storage.Object
+import qualified Data.Git.Parser as P
+
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString as B
+
+import Control.Exception (onException, SomeException)
+import qualified Control.Exception as E
+
+import Data.String
+import Data.Char (isHexDigit)
+
+newtype Zipped = Zipped { getZippedData :: L.ByteString }
+    deriving (Show,Eq)
+
+readZippedFile :: LocalPath -> IO Zipped
+readZippedFile fp = Zipped <$> readBinaryFileLazy fp
+
+dezip :: Zipped -> L.ByteString
+dezip = decompress . getZippedData
+
+isObjectPrefix :: [Char] -> Bool
+isObjectPrefix [a,b] = isHexDigit a && isHexDigit b
+isObjectPrefix _     = False
+
+-- loose object parsing
+parseHeader :: P.Parser ObjectHeader
+parseHeader = do
+        h <- P.takeWhile1 ((/=) 0x20)
+        _ <- P.byte 0x20
+        sz <- P.decimal :: P.Parser Int
+        return (objectTypeUnmarshall h, fromIntegral sz, Nothing)
+
+data HeaderType = HeaderTree | HeaderTag | HeaderCommit | HeaderBlob
+
+parseTreeHeader, parseTagHeader, parseCommitHeader, parseBlobHeader :: P.Parser HeaderType
+parseTreeHeader   = P.string "tree " >> parseLength >> P.byte 0 >> return HeaderTree
+parseTagHeader    = P.string "tag " >> parseLength >> P.byte 0 >> return HeaderTag
+parseCommitHeader = P.string "commit " >> parseLength >> P.byte 0 >> return HeaderCommit
+parseBlobHeader   = P.string "blob " >> parseLength >> P.byte 0 >> return HeaderBlob
+
+parseLength :: P.Parser Int
+parseLength = P.decimal
+
+parseObject :: L.ByteString -> Object
+parseObject = parseSuccess getOne
+  where
+    parseSuccess p = either (error . ("parseObject: " ++)) id . P.eitherParseChunks p . L.toChunks
+    getOne = do
+        hdrType <- parseTreeHeader <|> parseBlobHeader <|> parseCommitHeader <|> parseTagHeader
+        case hdrType of
+            HeaderTree   -> objectParseTree
+            HeaderTag    -> objectParseTag
+            HeaderCommit -> objectParseCommit
+            HeaderBlob   -> objectParseBlob
+
+
+-- | unmarshall an object (with header) from a bytestring.
+looseUnmarshall :: L.ByteString -> Object
+looseUnmarshall = parseObject
+
+-- | unmarshall an object (with header) from a zipped stream.
+looseUnmarshallZipped :: Zipped -> Object
+looseUnmarshallZipped = parseObject . dezip
+
+-- | unmarshall an object as (header, data) tuple from a bytestring
+looseUnmarshallRaw :: L.ByteString -> (ObjectHeader, ObjectData)
+looseUnmarshallRaw stream =
+        case L.findIndex ((==) 0) stream of
+                Nothing  -> error "object not right format. missing 0"
+                Just idx ->
+                        let (h, r) = L.splitAt (idx+1) stream in
+                        case P.maybeParseChunks parseHeader (L.toChunks h) of
+                                Nothing  -> error "cannot open object"
+                                Just hdr -> (hdr, r)
+
+-- | unmarshall an object as (header, data) tuple from a zipped stream
+looseUnmarshallZippedRaw :: Zipped -> (ObjectHeader, ObjectData)
+looseUnmarshallZippedRaw = looseUnmarshallRaw . dezip
+
+-- | read a specific ref from a loose object and returns an header and data.
+looseReadRaw :: LocalPath -> Ref -> IO (ObjectHeader, ObjectData)
+looseReadRaw repoPath ref = looseUnmarshallZippedRaw <$> readZippedFile (objectPathOfRef repoPath ref)
+
+-- | read only the header of a loose object.
+looseReadHeader :: LocalPath -> Ref -> IO ObjectHeader
+looseReadHeader repoPath ref = toHeader <$> readZippedFile (objectPathOfRef repoPath ref)
+  where
+    toHeader = either (error . ("parseHeader: " ++)) id . P.eitherParseChunks parseHeader . L.toChunks . dezip
+
+-- | read a specific ref from a loose object and returns an object
+looseRead :: LocalPath -> Ref -> IO Object
+looseRead repoPath ref = looseUnmarshallZipped <$> readZippedFile (objectPathOfRef repoPath ref)
+
+-- | check if a specific ref exists as loose object
+looseExists :: LocalPath -> Ref -> IO Bool
+looseExists repoPath ref = isFile (objectPathOfRef repoPath ref)
+
+-- | enumarate all prefixes available in the object store.
+looseEnumeratePrefixes :: LocalPath -> IO [[Char]]
+looseEnumeratePrefixes repoPath = filter isObjectPrefix <$> getDirectoryContents (repoPath </> fromString "objects")
+
+-- | enumerate all references available with a specific prefix.
+looseEnumerateWithPrefixFilter :: LocalPath -> String -> (Ref -> Bool) -> IO [Ref]
+looseEnumerateWithPrefixFilter repoPath prefix filterF =
+        filter filterF . map (fromHexString . (prefix ++)) . filter isRef <$> getDir (repoPath </> fromString "objects" </> fromString prefix)
+        where
+                getDir p = E.catch (getDirectoryContents p) (\(_::SomeException) -> return [])
+                isRef l = length l == 38
+
+looseEnumerateWithPrefix :: LocalPath -> String -> IO [Ref]
+looseEnumerateWithPrefix repoPath prefix =
+        looseEnumerateWithPrefixFilter repoPath prefix (const True)
+
+-- | marshall as lazy bytestring an object except deltas.
+looseMarshall :: Object -> L.ByteString
+looseMarshall obj
+        | objectIsDelta obj = error "cannot write delta object loose"
+        | otherwise         = L.concat [ L.fromChunks [hdrB], objData ]
+        where
+                objData = objectWrite obj
+                hdrB    = objectWriteHeader (objectToType obj) (fromIntegral $ L.length objData)
+
+-- | create a new blob on a temporary location and on success move it to
+-- the object store with its digest name.
+looseWriteBlobFromFile :: LocalPath -> LocalPath -> IO Ref
+looseWriteBlobFromFile repoPath file = do
+        fsz <- getSize file
+        let hdr = objectWriteHeader TypeBlob (fromIntegral fsz)
+        tmpPath <- objectTemporaryPath repoPath
+        flip onException (removeFile tmpPath) $ do
+                (ref, npath) <- withFileWriter tmpPath $ \fw -> do
+                        fileWriterOutput fw hdr
+                        withFile file ReadMode $ \h -> loop h fw
+                        digest <- fileWriterGetDigest fw
+                        return (digest, objectPathOfRef repoPath digest)
+                exists <- isFile npath
+                when exists $ error "destination already exists"
+                rename tmpPath npath
+                return ref
+    where loop h fw = do
+                r <- B.hGet h (32*1024)
+                if B.null r
+                    then return ()
+                    else fileWriterOutput fw r >> loop h fw
+
+-- | write an object to disk as a loose reference.
+-- use looseWriteBlobFromFile for efficiently writing blobs when being commited from a file.
+looseWrite :: LocalPath -> Object -> IO Ref
+looseWrite repoPath obj = createParentDirectory path
+                       >> isFile path
+                       >>= \exists -> unless exists (writeFileLazy path $ compress content)
+                       >> return ref
+        where
+                path    = objectPathOfRef repoPath ref
+                content = looseMarshall obj
+                ref     = hashLBS content
+                writeFileLazy p bs = withFile p WriteMode (\h -> L.hPut h bs)
+
+getDirectoryContents :: LocalPath -> IO [String]
+getDirectoryContents p = listDirectoryFilename p
diff --git a/Data/Git/Storage/Object.hs b/Data/Git/Storage/Object.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/Object.hs
@@ -0,0 +1,355 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE CPP #-}
+-- |
+-- Module      : Data.Git.Storage.Object
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+module Data.Git.Storage.Object
+        ( ObjectLocation(..)
+        , ObjectType(..)
+        , ObjectHeader
+        , ObjectData
+        , ObjectPtr(..)
+        , Object(..)
+        , ObjectInfo(..)
+        , Objectable(..)
+        , objectToType
+        , objectTypeMarshall
+        , objectTypeUnmarshall
+        , objectTypeIsDelta
+        , objectIsDelta
+        , objectToTree
+        , objectToCommit
+        , objectToTag
+        , objectToBlob
+        -- * parsing function
+        , treeParse
+        , commitParse
+        , tagParse
+        , blobParse
+        , objectParseTree
+        , objectParseCommit
+        , objectParseTag
+        , objectParseBlob
+        -- * writing function
+        , objectWriteHeader
+        , objectWrite
+        , objectHash
+        ) where
+
+import Data.Git.Ref
+import Data.Git.Types
+import Data.Git.Imports
+import qualified Data.Git.Parser as P
+
+import Data.Byteable (toBytes)
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Char8 as BC
+import qualified Data.ByteString.Lazy as L
+
+import Data.List (intersperse)
+import Data.Word
+import Text.Printf
+
+#if MIN_VERSION_bytestring(0,10,0)
+import Data.ByteString.Lazy.Builder hiding (word8)
+#else
+import qualified Data.ByteString.Lazy.Char8 as LC
+
+-- tiny builder interface like for bytestring < 0.10 that
+-- use normal lazy bytestring concat.
+string7 :: String -> L.ByteString
+string7 = LC.pack
+
+byteString :: ByteString -> L.ByteString
+byteString = LC.fromChunks . (:[])
+
+toLazyByteString = id
+#endif
+
+-- | location of an object in the database
+data ObjectLocation = NotFound | Loose Ref | Packed Ref Word64
+        deriving (Show,Eq)
+
+-- | Delta objects points to some others objects in the database
+-- either as offset in the pack or as a direct reference.
+data ObjectPtr = PtrRef Ref | PtrOfs Word64 deriving (Show,Eq)
+
+type ObjectHeader = (ObjectType, Word64, Maybe ObjectPtr)
+
+type ObjectData = L.ByteString
+
+-- | Raw objects infos have an header (type, size, ptr),
+-- the data and a pointers chains to parents for resolved objects.
+data ObjectInfo = ObjectInfo
+        { oiHeader :: ObjectHeader
+        , oiData   :: ObjectData
+        , oiChains :: [ObjectPtr]
+        } deriving (Show,Eq)
+
+-- | describe a git object, that could of 6 differents types:
+-- tree, blob, commit, tag and deltas (offset or ref).
+-- the deltas one are only available in packs.
+data Object = ObjCommit   Commit
+            | ObjTag      Tag
+            | ObjBlob     Blob
+            | ObjTree     Tree
+            | ObjDeltaOfs DeltaOfs
+            | ObjDeltaRef DeltaRef
+            deriving (Show,Eq)
+
+class Objectable a where
+        getType  :: a -> ObjectType
+        getRaw   :: a -> L.ByteString
+        isDelta  :: a -> Bool
+        toObject :: a -> Object
+
+objectToType :: Object -> ObjectType
+objectToType (ObjTree _)     = TypeTree
+objectToType (ObjBlob _)     = TypeBlob
+objectToType (ObjCommit _)   = TypeCommit
+objectToType (ObjTag _)      = TypeTag
+objectToType (ObjDeltaOfs _) = TypeDeltaOff
+objectToType (ObjDeltaRef _) = TypeDeltaRef
+
+objectTypeMarshall :: ObjectType -> String
+objectTypeMarshall TypeTree   = "tree"
+objectTypeMarshall TypeBlob   = "blob"
+objectTypeMarshall TypeCommit = "commit"
+objectTypeMarshall TypeTag    = "tag"
+objectTypeMarshall _          = error "deltas cannot be marshalled"
+
+objectTypeUnmarshall :: ByteString -> ObjectType
+objectTypeUnmarshall "tree"   = TypeTree
+objectTypeUnmarshall "blob"   = TypeBlob
+objectTypeUnmarshall "commit" = TypeCommit
+objectTypeUnmarshall "tag"    = TypeTag
+objectTypeUnmarshall _        = error "unknown object type"
+
+objectTypeIsDelta :: ObjectType -> Bool
+objectTypeIsDelta TypeDeltaOff = True
+objectTypeIsDelta TypeDeltaRef = True
+objectTypeIsDelta _            = False
+
+objectIsDelta :: Object -> Bool
+objectIsDelta (ObjDeltaOfs _) = True
+objectIsDelta (ObjDeltaRef _) = True
+objectIsDelta _               = False
+
+objectToTree :: Object -> Maybe Tree
+objectToTree (ObjTree tree) = Just tree
+objectToTree _              = Nothing
+
+objectToCommit :: Object -> Maybe Commit
+objectToCommit (ObjCommit commit) = Just commit
+objectToCommit _                  = Nothing
+
+objectToTag :: Object -> Maybe Tag
+objectToTag (ObjTag tag) = Just tag
+objectToTag _            = Nothing
+
+objectToBlob :: Object -> Maybe Blob
+objectToBlob (ObjBlob blob) = Just blob
+objectToBlob _              = Nothing
+
+octal :: P.Parser Int
+octal = B.foldl' step 0 `fmap` P.takeWhile1 isOct
+  where isOct w = w >= 0x30 && w <= 0x37
+        step a w = a * 8 + fromIntegral (w - 0x30)
+
+modeperm :: P.Parser ModePerm
+modeperm = ModePerm . fromIntegral <$> octal
+
+-- | parse a tree content
+treeParse :: P.Parser Tree
+treeParse = Tree <$> parseEnts
+    where parseEnts = P.hasMore >>= \b -> if b then liftM2 (:) parseEnt parseEnts else return []
+          parseEnt = liftM3 (,,) modeperm parseEntName (P.byte 0 >> P.referenceBin)
+          parseEntName = entName <$> (P.skipASCII ' ' >> P.takeWhile (/= 0))
+
+-- | parse a blob content
+blobParse :: P.Parser Blob
+blobParse = (Blob . L.fromChunks . (:[]) <$> P.takeAll)
+
+-- | parse a commit content
+commitParse :: P.Parser Commit
+commitParse = do
+        tree <- P.string "tree " >> P.referenceHex
+        P.skipEOL
+        parents   <- many parseParentRef
+        author    <- P.string "author " >> parsePerson
+        committer <- P.string "committer " >> parsePerson
+        encoding  <- optional (P.string "encoding " >> P.tillEOL)
+        extras    <- many parseExtra
+        P.skipEOL
+        message <- P.takeAll
+        return $ Commit tree parents author committer encoding extras message
+        where
+                parseParentRef = do
+                        tree <- P.string "parent " >> P.referenceHex
+                        P.skipEOL
+                        return tree
+                parseExtra = do
+                        b <- P.anyByte
+                        if b == 0xa
+                            then fail "no extra"
+                            else do
+                                r <- P.tillEOL
+                                P.skipEOL
+                                v <- concatLines <$> many (P.string " " *> P.tillEOL <* P.skipEOL)
+                                return $ CommitExtra (b `B.cons` r) v
+                concatLines = B.concat . intersperse (B.pack [0xa])
+
+-- | parse a tag content
+tagParse :: P.Parser Tag
+tagParse = do
+        object <- P.string "object " >> P.referenceHex
+        P.skipEOL
+        type_ <- objectTypeUnmarshall <$> (P.string "type " >> P.tillEOL)
+        P.skipEOL
+        tag   <- P.string "tag " >> P.tillEOL -- PC.takeTill ((==) 0x0a)
+        P.skipEOL
+        tagger <- P.string "tagger " >> parsePerson
+        P.skipEOL
+        signature <- P.takeAll
+        return $ Tag object type_ tag tagger signature
+
+parsePerson :: P.Parser Person
+parsePerson = do
+        name <- B.init <$> P.takeUntilASCII '<'
+        P.skipASCII '<'
+        email <- P.takeUntilASCII '>'
+        _ <- P.string "> "
+        time <- P.decimal :: P.Parser Integer
+        _ <- P.string " "
+        timezoneSign <- maybe 1 id <$> optional ((const 1 <$> ascii '+') <|> (const (-1) <$> ascii '-'))
+        timezoneFmt  <- P.decimal
+        let (h,m)    = timezoneFmt `divMod` 100
+            timezone = timezoneSign * (h * 60 + m)
+        P.skipEOL
+        return $ Person name email (gitTime time timezone)
+
+ascii :: Char -> P.Parser ()
+ascii c = P.byte (asciiChar c)
+
+asciiChar :: Char -> Word8
+asciiChar c
+    | cp < 0x80 = fromIntegral cp
+    | otherwise = error ("char " <> show c <> " not valid ASCII")
+  where cp = fromEnum c
+
+objectParseTree, objectParseCommit, objectParseTag, objectParseBlob :: P.Parser Object
+objectParseTree   = ObjTree <$> treeParse
+objectParseCommit = ObjCommit <$> commitParse
+objectParseTag    = ObjTag <$> tagParse
+objectParseBlob   = ObjBlob <$> blobParse
+
+-- header of loose objects, but also useful for any object to determine object's hash
+objectWriteHeader :: ObjectType -> Word64 -> ByteString
+objectWriteHeader ty sz = BC.pack (objectTypeMarshall ty ++ " " ++ show sz ++ [ '\0' ])
+
+objectWrite :: Object -> L.ByteString
+objectWrite (ObjCommit commit) = commitWrite commit
+objectWrite (ObjTag tag)       = tagWrite tag
+objectWrite (ObjBlob blob)     = blobWrite blob
+objectWrite (ObjTree tree)     = treeWrite tree
+objectWrite _                  = error "delta cannot be marshalled"
+
+treeWrite :: Tree -> L.ByteString
+treeWrite (Tree ents) = toLazyByteString $ mconcat $ concatMap writeTreeEnt ents
+    where writeTreeEnt (ModePerm perm,name,ref) =
+                [ string7 (printf "%o" perm)
+                , string7 " "
+                , byteString $ toBytes name
+                , string7 "\0"
+                , byteString $ toBinary ref
+                ]
+
+commitWrite :: Commit -> L.ByteString
+commitWrite (Commit tree parents author committer encoding extra msg) =
+    toLazyByteString $ mconcat els
+    where
+          toNamedRef s r = mconcat [string7 s, byteString (toHex r),eol]
+          toParent       = toNamedRef "parent "
+          toCommitExtra (CommitExtra k v) = [byteString k, eol] ++
+                                            (concatMap (\l -> [byteString " ", byteString l, eol]) $ linesLast v)
+
+          linesLast b
+            | B.length b > 0 && B.last b == 0xa = BC.lines b ++ [ "" ]
+            | otherwise                         = BC.lines b
+          els = [toNamedRef "tree " tree ]
+             ++ map toParent parents
+             ++ [byteString $ writeName "author" author, eol
+                ,byteString $ writeName "committer" committer, eol
+                ,maybe (byteString B.empty) (byteString) encoding -- FIXME need eol
+                ]
+             ++ concatMap toCommitExtra extra
+             ++ [eol
+                ,byteString msg
+                ]
+
+tagWrite :: Tag -> L.ByteString
+tagWrite (Tag ref ty tag tagger signature) =
+    toLazyByteString $ mconcat els
+    where els = [ string7 "object ", byteString (toHex ref), eol
+                , string7 "type ", string7 (objectTypeMarshall ty), eol
+                , string7 "tag ", byteString tag, eol
+                , byteString $ writeName "tagger" tagger, eol
+                , eol
+                , byteString signature
+                ]
+
+eol :: Builder
+eol = string7 "\n"
+
+blobWrite :: Blob -> L.ByteString
+blobWrite (Blob bData) = bData
+
+instance Objectable Blob where
+        getType _ = TypeBlob
+        getRaw    = blobWrite
+        toObject  = ObjBlob
+        isDelta   = const False
+
+instance Objectable Commit where
+        getType _ = TypeCommit
+        getRaw    = commitWrite
+        toObject  = ObjCommit
+        isDelta   = const False
+
+instance Objectable Tag where
+        getType _ = TypeTag
+        getRaw    = tagWrite
+        toObject  = ObjTag
+        isDelta   = const False
+
+instance Objectable Tree where
+        getType _ = TypeTree
+        getRaw    = treeWrite
+        toObject  = ObjTree
+        isDelta   = const False
+
+instance Objectable DeltaOfs where
+        getType _ = TypeDeltaOff
+        getRaw    = error "delta offset cannot be marshalled"
+        toObject  = ObjDeltaOfs
+        isDelta   = const True
+
+instance Objectable DeltaRef where
+        getType _ = TypeDeltaRef
+        getRaw    = error "delta ref cannot be marshalled"
+        toObject  = ObjDeltaRef
+        isDelta   = const True
+
+objectHash :: ObjectType -> Word64 -> L.ByteString -> Ref
+objectHash ty w lbs = hashLBS $ L.fromChunks (objectWriteHeader ty w : L.toChunks lbs)
+
+-- used for objectWrite for commit and tag
+writeName :: ByteString -> Person -> ByteString
+writeName label (Person name email locTime) =
+        B.concat [label, " ", name, " <", email, "> ", BC.pack timeStr]
+  where timeStr = show locTime
diff --git a/Data/Git/Storage/Pack.hs b/Data/Git/Storage/Pack.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/Pack.hs
@@ -0,0 +1,154 @@
+-- |
+-- Module      : Data.Git.Storage.Pack
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE OverloadedStrings #-}
+module Data.Git.Storage.Pack
+        ( PackedObjectInfo(..)
+        , PackedObjectRaw
+        -- * Enumerators of packs
+        , packEnumerate
+        -- * Helpers to process packs
+        , packOpen
+        , packClose
+        -- * Command for the content of a pack
+        , packReadHeader
+        , packReadMapAtOffset
+        , packReadAtOffset
+        , packReadRawAtOffset
+        , packEnumerateObjects
+        -- * turn a packed object into a
+        , packedObjectToObject
+        , packObjectFromRaw
+        ) where
+
+import Control.Arrow (second)
+
+import Data.Bits
+import Data.List
+import qualified Data.ByteString.Lazy as L
+
+import qualified Data.Git.Parser as P
+import Data.Git.Internal
+import Data.Git.Imports
+import Data.Git.Path
+import Data.Git.Storage.Object
+import Data.Git.Delta
+import Data.Git.Ref
+import Data.Git.Types
+import Data.Git.OS
+import Data.Git.Storage.FileReader
+
+import Data.Word
+
+type PackedObjectRaw = (PackedObjectInfo, L.ByteString)
+
+data PackedObjectInfo = PackedObjectInfo
+        { poiType       :: ObjectType
+        , poiOffset     :: Word64
+        , poiSize       :: Word64
+        , poiActualSize :: Word64
+        , poiExtra      :: Maybe ObjectPtr
+        } deriving (Show,Eq)
+
+-- | Enumerate the pack refs available in this repository.
+packEnumerate :: LocalPath -> IO [Ref]
+packEnumerate repoPath = map onlyHash . filter isPackFile <$> listDirectoryFilename (repoPath </> "objects" </> "pack")
+  where
+        isPackFile :: String -> Bool
+        isPackFile x = ".pack" `isSuffixOf` x
+        onlyHash = fromHexString . takebut 5 . drop 5
+        takebut n l = take (length l - n) l
+
+-- | open a pack
+packOpen :: LocalPath -> Ref -> IO FileReader
+packOpen repoPath packRef = openFile (packPath repoPath packRef) ReadMode >>= fileReaderNew False
+
+-- | close a pack
+packClose :: FileReader -> IO ()
+packClose = fileReaderClose
+
+-- | return the number of entries in this pack
+packReadHeader :: LocalPath -> Ref -> IO Word32
+packReadHeader repoPath packRef =
+        withFileReader (packPath repoPath packRef) $ \filereader ->
+                fileReaderParse filereader parseHeader
+        where parseHeader = do
+                packMagic <- P.word32
+                when (packMagic /= 0x5041434b) $ error "not a git packfile"
+                ver <- P.word32
+                when (ver /= 2) $ error ("pack file version not supported: " ++ show ver)
+                P.word32
+
+-- | read an object at a specific position using a map function on the objectData
+packReadMapAtOffset :: FileReader -> Word64 -> (L.ByteString -> L.ByteString) -> IO (Maybe Object)
+packReadMapAtOffset fr offset mapData = fileReaderSeek fr offset >> getNextObject fr mapData
+
+-- | read an object at a specific position
+packReadAtOffset :: FileReader -> Word64 -> IO (Maybe Object)
+packReadAtOffset fr offset = packReadMapAtOffset fr offset id
+
+-- | read a raw representation at a specific position
+packReadRawAtOffset :: FileReader -> Word64 -> IO (PackedObjectRaw)
+packReadRawAtOffset fr offset = fileReaderSeek fr offset >> getNextObjectRaw fr
+
+-- | enumerate all objects in this pack and callback to f for reach raw objects
+packEnumerateObjects :: LocalPath -> Ref -> Int -> (PackedObjectRaw -> IO a) -> IO ()
+packEnumerateObjects repoPath packRef entries f =
+        withFileReader (packPath repoPath packRef) $ \filebuffer -> do
+                fileReaderSeek filebuffer 12
+                parseNext filebuffer entries
+        where
+                parseNext :: FileReader -> Int -> IO ()
+                parseNext _  0    = return ()
+                parseNext fr ents = getNextObjectRaw fr >>= f >> parseNext fr (ents-1)
+
+getNextObject :: FileReader -> (L.ByteString -> L.ByteString) -> IO (Maybe Object)
+getNextObject fr mapData =
+        packedObjectToObject . second mapData <$> getNextObjectRaw fr
+
+packedObjectToObject :: (PackedObjectInfo, L.ByteString) -> Maybe Object
+packedObjectToObject (PackedObjectInfo { poiType = ty, poiExtra = extra }, objData) =
+        packObjectFromRaw (ty, extra, objData)
+
+packObjectFromRaw :: (ObjectType, Maybe ObjectPtr, L.ByteString) -> Maybe Object
+packObjectFromRaw (TypeCommit, Nothing, objData) = P.maybeParseChunks objectParseCommit (L.toChunks objData)
+packObjectFromRaw (TypeTree, Nothing, objData)   = P.maybeParseChunks objectParseTree (L.toChunks objData)
+packObjectFromRaw (TypeBlob, Nothing, objData)   = P.maybeParseChunks objectParseBlob (L.toChunks objData)
+packObjectFromRaw (TypeTag, Nothing, objData)    = P.maybeParseChunks objectParseTag (L.toChunks objData)
+packObjectFromRaw (TypeDeltaOff, Just (PtrOfs o), objData) = toObject . DeltaOfs o <$> deltaRead (L.toChunks objData)
+packObjectFromRaw (TypeDeltaRef, Just (PtrRef r), objData) = toObject . DeltaRef r <$> deltaRead (L.toChunks objData)
+packObjectFromRaw _                              = error "can't happen unless someone change getNextObjectRaw"
+
+getNextObjectRaw :: FileReader -> IO PackedObjectRaw
+getNextObjectRaw fr = do
+        sobj       <- fileReaderGetPos fr
+        (ty, size) <- fileReaderParse fr parseObjectHeader
+        extra      <- case ty of
+                TypeDeltaRef -> Just . PtrRef . fromBinary <$> fileReaderGetBS 20 fr
+                TypeDeltaOff -> Just . PtrOfs . deltaOffFromList <$> fileReaderGetVLF fr
+                _            -> return Nothing
+        objData    <- fileReaderInflateToSize fr size
+        eobj       <- fileReaderGetPos fr
+
+        return (PackedObjectInfo ty sobj (eobj - sobj) size extra, objData)
+        where
+                parseObjectHeader = do
+                        (m, ty, sz) <- splitFirst <$> P.anyByte
+                        size <- if m then (sz +) <$> getNextSize 4 else return sz
+                        return (ty, size)
+                        where
+                                getNextSize n = do
+                                        (c, sz) <- splitOther n <$> P.anyByte
+                                        if c then (sz +) <$> getNextSize (n+7) else return sz
+
+                                splitFirst :: Word8 -> (Bool, ObjectType, Word64)
+                                splitFirst w = (w `testBit` 7, toEnum $ fromIntegral ((w `shiftR` 4) .&. 0x7), fromIntegral (w .&. 0xf))
+                                splitOther n w = (w `testBit` 7, fromIntegral (w .&. 0x7f) `shiftL` n)
+
+                deltaOffFromList (x:xs) = foldl' acc (fromIntegral (x `clearBit` 7)) xs
+                        where acc a w = ((a+1) `shiftL` 7) + fromIntegral (w `clearBit` 7)
+                deltaOffFromList [] = error "cannot happen"
diff --git a/Data/Git/Storage/PackIndex.hs b/Data/Git/Storage/PackIndex.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Storage/PackIndex.hs
@@ -0,0 +1,183 @@
+-- |
+-- Module      : Data.Git.Storage.PackIndex
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE OverloadedStrings, BangPatterns #-}
+module Data.Git.Storage.PackIndex
+        ( PackIndexHeader(..)
+        , PackIndex(..)
+
+        -- * handles and enumeration
+        , packIndexOpen
+        , packIndexClose
+        , withPackIndex
+        , packIndexEnumerate
+
+        -- * read from packIndex
+        , packIndexHeaderGetNbWithPrefix
+        , packIndexGetReferenceLocation
+        , packIndexGetReferencesWithPrefix
+        , packIndexReadHeader
+        , packIndexRead
+        , packIndexGetHeader
+        ) where
+
+import Data.List
+import Data.Bits
+import Data.Word
+import Data.ByteString (ByteString)
+
+import Data.Vector (Vector, (!))
+import qualified Data.Vector as V
+
+import Data.Git.Internal
+import Data.Git.Imports
+import Data.Git.OS
+import Data.Git.Storage.FileReader
+import Data.Git.Path
+import Data.Git.Ref
+import qualified Data.Git.Parser as P
+
+-- | represent an packIndex header with the version and the fanout table
+data PackIndexHeader = PackIndexHeader !Word32 !(Vector Word32)
+        deriving (Show,Eq)
+
+data PackIndex = PackIndex
+        { packIndexSha1s        :: Vector Ref
+        , packIndexCRCs         :: Vector Word32
+        , packIndexPackoffs     :: Vector Word32
+        , packIndexPackChecksum :: Ref
+        , packIndexChecksum     :: Ref
+        }
+
+-- | enumerate every indexes file in the pack directory
+packIndexEnumerate :: LocalPath -> IO [Ref]
+packIndexEnumerate repoPath = map onlyHash . filter isPackFile <$> listDirectoryFilename (repoPath </> "objects" </> "pack")
+  where
+        isPackFile :: String -> Bool
+        isPackFile x = ".idx" `isSuffixOf` x && "pack-" `isPrefixOf` x
+        onlyHash = fromHexString . takebut 4 . drop 5
+        takebut n l = take (length l - n) l
+
+-- | open an index
+packIndexOpen :: LocalPath -> Ref -> IO FileReader
+packIndexOpen repoPath indexRef = openFile (indexPath repoPath indexRef) ReadMode >>= fileReaderNew False
+
+-- | close an index
+packIndexClose :: FileReader -> IO ()
+packIndexClose = fileReaderClose
+
+-- | variant of withFile on the index file and with a FileReader
+withPackIndex :: LocalPath -> Ref -> (FileReader -> IO a) -> IO a
+withPackIndex repoPath indexRef = withFileReader (indexPath repoPath indexRef)
+
+-- | returns the number of references, referenced in this index.
+packIndexHeaderGetSize :: PackIndexHeader -> Word32
+packIndexHeaderGetSize (PackIndexHeader _ indexes) = indexes ! 255
+
+-- | byte size of an packIndex header.
+packIndexHeaderByteSize :: Int
+packIndexHeaderByteSize = 2*4 {- header -} + 256*4 {- fanout table -}
+
+-- | get the number of reference in this index with a specific prefix
+packIndexHeaderGetNbWithPrefix :: PackIndexHeader -> Int -> Word32
+packIndexHeaderGetNbWithPrefix (PackIndexHeader _ indexes) n
+        | n < 0 || n > 255 = 0
+        | n == 0           = indexes ! 0
+        | otherwise        = (indexes ! n) - (indexes ! (n-1))
+
+-- | fold on refs with a specific prefix
+packIndexHeaderFoldRef :: PackIndexHeader -> FileReader -> Int -> (a -> Word32 -> Ref -> (a, Bool)) -> a -> IO a
+packIndexHeaderFoldRef idxHdr@(PackIndexHeader _ indexes) fr refprefix f initAcc
+        | nb == 0   = return initAcc
+        | otherwise = do
+                let spos = (indexes ! refprefix) - nb
+                fileReaderSeek fr (fromIntegral (sha1Offset + spos * 20))
+                loop nb initAcc
+        where
+                loop 0 acc = return acc
+                loop n acc = do
+                        b <- fromBinary <$> fileReaderGetBS 20 fr
+                        let (!nacc, terminate) = f acc (nb-n) b
+                        if terminate
+                                then return nacc
+                                else loop (n-1) nacc
+                nb         = packIndexHeaderGetNbWithPrefix idxHdr refprefix
+                (sha1Offset,_,_) = packIndexOffsets idxHdr
+
+-- | return the reference offset in the packfile if found
+packIndexGetReferenceLocation :: PackIndexHeader -> FileReader -> Ref -> IO (Maybe Word64)
+packIndexGetReferenceLocation idxHdr@(PackIndexHeader _ indexes) fr ref = do
+        mrpos <- packIndexHeaderFoldRef idxHdr fr refprefix f Nothing
+        case mrpos of
+                Nothing   -> return Nothing
+                Just rpos -> do
+                        let spos = (indexes ! refprefix) - nb
+                        fileReaderSeek fr (fromIntegral (packOffset + 4 * (spos+rpos)))
+                        Just . fromIntegral . be32 <$> fileReaderGetBS 4 fr
+        where
+                f acc rpos rref = if ref == rref then (Just rpos,True) else (acc,False)
+                refprefix  = refPrefix ref
+                nb         = packIndexHeaderGetNbWithPrefix idxHdr refprefix
+                (_,_,packOffset) = packIndexOffsets idxHdr
+
+-- | get all references that start by prefix.
+packIndexGetReferencesWithPrefix :: PackIndexHeader -> FileReader -> String -> IO [Ref]
+packIndexGetReferencesWithPrefix idxHdr fr prefix =
+        packIndexHeaderFoldRef idxHdr fr refprefix f []
+        where
+                f acc _ ref = case cmpPrefix prefix ref of
+                        GT -> (acc    ,False)
+                        EQ -> (ref:acc,False)
+                        LT -> (acc    ,True)
+                refprefix   = read ("0x" ++ take 2 prefix)
+
+-- | returns absolute offset in the index file of the sha1s, the crcs and the packfiles offset.
+packIndexOffsets :: PackIndexHeader -> (Word32, Word32, Word32)
+packIndexOffsets idx = (packIndexSha1sOffset, packIndexCRCsOffset, packIndexPackOffOffset)
+        where
+                packIndexPackOffOffset = packIndexCRCsOffset + crcsTableSz
+                packIndexCRCsOffset    = packIndexSha1sOffset + sha1TableSz
+                packIndexSha1sOffset   = fromIntegral packIndexHeaderByteSize
+                crcsTableSz        = 4 * sz
+                sha1TableSz        = 20 * sz
+                sz                 = packIndexHeaderGetSize idx
+
+-- | parse index header
+parsePackIndexHeader :: P.Parser PackIndexHeader
+parsePackIndexHeader = do
+        magic   <- P.word32
+        when (magic /= 0xff744f63) $ error "wrong magic number for packIndex"
+        ver     <- P.word32
+        when (ver /= 2) $ error "unsupported packIndex version"
+        fanouts <- V.replicateM 256 P.word32
+        return $ PackIndexHeader ver fanouts
+
+-- | read index header from an index filereader
+packIndexReadHeader :: FileReader -> IO PackIndexHeader
+packIndexReadHeader fr = fileReaderSeek fr 0 >> fileReaderParse fr parsePackIndexHeader
+
+-- | get index header from an index reference
+packIndexGetHeader :: LocalPath -> Ref -> IO PackIndexHeader
+packIndexGetHeader repoPath indexRef = withPackIndex repoPath indexRef $ packIndexReadHeader
+
+-- | read all index
+packIndexRead :: LocalPath -> Ref -> IO (PackIndexHeader, (Vector Ref, Vector Word32, Vector Word32, [ByteString], Ref, Ref))
+packIndexRead repoPath indexRef = do
+        withPackIndex repoPath indexRef $ \fr -> do
+                idx <- fileReaderParse fr parsePackIndexHeader
+                liftM2 (,) (return idx) (fileReaderParse fr (parsePackIndex $ packIndexHeaderGetSize idx))
+        where parsePackIndex sz = do
+                sha1s     <- V.replicateM (fromIntegral sz) P.referenceBin
+                crcs      <- V.replicateM (fromIntegral sz) P.word32
+                packoffs  <- V.replicateM (fromIntegral sz) P.word32
+                let nbLarge = length $ filter (== True) $ map (\packoff -> packoff `testBit` 31) $ V.toList packoffs
+                largeoffs <- replicateM nbLarge (P.take 4)
+                packfileChecksum <- P.referenceBin
+                idxfileChecksum  <- P.referenceBin
+                -- large packfile offsets
+                -- trailer
+                return (sha1s, crcs, packoffs, largeoffs, packfileChecksum, idxfileChecksum)
diff --git a/Data/Git/Types.hs b/Data/Git/Types.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/Types.hs
@@ -0,0 +1,227 @@
+-- |
+-- Module      : Data.Git.Object
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+{-# LANGUAGE DeriveDataTypeable #-}
+module Data.Git.Types
+    (
+    -- * Type of types
+      ObjectType(..)
+    -- * Main git types
+    , Tree(..)
+    , Commit(..)
+    , CommitExtra(..)
+    , Blob(..)
+    , Tag(..)
+    , Person(..)
+    , EntName
+    , entName
+    , EntPath
+    , entPathAppend
+    -- * modeperm type
+    , ModePerm(..)
+    , FilePermissions(..)
+    , ObjectFileType(..)
+    , getPermission
+    , getFiletype
+    -- * time type
+    , GitTime(..)
+    , gitTime
+    , gitTimeToLocal
+    -- * Pack delta types
+    , DeltaOfs(..)
+    , DeltaRef(..)
+    -- * Basic types part of other bigger types
+    , TreeEnt
+    ) where
+
+import Data.Word
+import Data.Bits
+import Data.Byteable
+import Data.String
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as L
+
+import Data.Git.Ref
+import Data.Git.Delta
+import Data.Git.Imports
+import Data.Hourglass (Elapsed, TimezoneOffset(..)
+                      , timePrint, timeConvert
+                      , Time(..), Timeable(..)
+                      , LocalTime, localTimeSetTimezone, localTimeFromGlobal)
+import Data.Data
+import qualified Data.ByteString.UTF8 as UTF8
+
+-- | type of a git object.
+data ObjectType =
+      TypeTree
+    | TypeBlob
+    | TypeCommit
+    | TypeTag
+    | TypeDeltaOff
+    | TypeDeltaRef
+    deriving (Show,Eq,Data,Typeable)
+
+-- | Git time is number of seconds since unix epoch in the UTC zone with
+-- the current timezone associated
+data GitTime = GitTime
+    { gitTimeUTC      :: Elapsed
+    , gitTimeTimezone :: TimezoneOffset
+    } deriving (Eq)
+
+instance Timeable GitTime where
+    timeGetNanoSeconds _ = 0
+    timeGetElapsedP (GitTime t _) = timeConvert t
+    timeGetElapsed  (GitTime t _) = t
+instance Time GitTime where
+    timeFromElapsedP e = GitTime (timeGetElapsed e) (TimezoneOffset 0)
+    timeFromElapsed e = GitTime e (TimezoneOffset 0)
+
+instance Show GitTime where
+    show (GitTime t tz) =
+        timePrint "EPOCH" t ++ " " ++ show tz
+
+gitTime :: Integer -> Int -> GitTime
+gitTime seconds tzMins =
+    GitTime (fromIntegral seconds) (TimezoneOffset tzMins)
+
+gitTimeToLocal :: GitTime -> LocalTime Elapsed
+gitTimeToLocal (GitTime t tz) =
+    localTimeSetTimezone tz (localTimeFromGlobal t)
+
+-- | the enum instance is useful when marshalling to pack file.
+instance Enum ObjectType where
+    fromEnum TypeCommit   = 0x1
+    fromEnum TypeTree     = 0x2
+    fromEnum TypeBlob     = 0x3
+    fromEnum TypeTag      = 0x4
+    fromEnum TypeDeltaOff = 0x6
+    fromEnum TypeDeltaRef = 0x7
+
+    toEnum 0x1 = TypeCommit
+    toEnum 0x2 = TypeTree
+    toEnum 0x3 = TypeBlob
+    toEnum 0x4 = TypeTag
+    toEnum 0x6 = TypeDeltaOff
+    toEnum 0x7 = TypeDeltaRef
+    toEnum n   = error ("not a valid object: " ++ show n)
+
+newtype ModePerm = ModePerm Word32
+    deriving (Show,Eq)
+
+getPermission :: ModePerm -> FilePermissions
+getPermission (ModePerm modeperm) =
+    let owner = (modeperm .&. 0x700) `shiftR` 6
+        group = (modeperm .&. 0x70) `shiftR` 3
+        other = modeperm .&. 0x7
+     in FilePermissions (fromIntegral owner) (fromIntegral group) (fromIntegral other)
+
+getFiletype :: ModePerm -> ObjectFileType
+getFiletype (ModePerm modeperm) =
+    case modeperm `shiftR` 12 of
+        _ -> error "filetype unknown"
+
+-- | Git object file type
+data ObjectFileType =
+      FileTypeDirectory
+    | FileTypeRegularFile
+    | FileTypeSymbolicLink
+    | FileTypeGitLink
+    deriving (Show,Eq)
+
+-- | traditional unix permission for owner, group and permissions
+data FilePermissions = FilePermissions
+    { getOwnerPerm :: {-# UNPACK #-} !Perm
+    , getGroupPerm :: {-# UNPACK #-} !Perm
+    , getOtherPerm :: {-# UNPACK #-} !Perm
+    } deriving (Show,Eq)
+
+-- | a bitfield representing a typical unix permission:
+-- * bit 0 represents the read permission
+-- * bit 1 represents the write permission
+-- * bit 2 represents the execute permission
+type Perm = Word8
+
+-- | Entity name
+newtype EntName = EntName ByteString
+    deriving (Eq,Ord)
+instance Show EntName where
+    show (EntName e) = UTF8.toString e
+instance IsString EntName where
+    fromString s = entName $ UTF8.fromString s
+instance Byteable EntName where
+    toBytes (EntName n) = n
+
+entName :: ByteString -> EntName
+entName bs
+    | B.elem slash bs = error ("entity name " ++ show bs ++ " contains an invalid '/' character")
+    | otherwise       = EntName bs
+  where slash = 47
+
+entPathAppend :: EntPath -> EntName -> EntPath
+entPathAppend l e = l ++ [e]
+
+type EntPath = [EntName]
+
+-- | represent one entry in the tree
+-- (permission,file or directory name,blob or tree ref)
+-- name should maybe a filepath, but not sure about the encoding.
+type TreeEnt = (ModePerm,EntName,Ref)
+
+-- | an author or committer line
+-- has the format: name <email> time timezone
+-- FIXME: should be a string, but I don't know if the data is stored
+-- consistantly in one encoding (UTF8)
+data Person = Person
+    { personName  :: ByteString
+    , personEmail :: ByteString
+    , personTime  :: GitTime
+    } deriving (Show,Eq)
+
+-- | Represent a root tree with zero to many tree entries.
+data Tree = Tree { treeGetEnts :: [TreeEnt] } deriving (Show,Eq)
+
+instance Monoid Tree where
+    mempty                      = Tree []
+    mappend (Tree e1) (Tree e2) = Tree (e1 ++ e2)
+    mconcat trees               = Tree $ concatMap treeGetEnts trees
+
+-- | Represent a binary blob.
+data Blob = Blob { blobGetContent :: L.ByteString } deriving (Show,Eq)
+
+-- | Represent a commit object.
+data Commit = Commit
+    { commitTreeish   :: Ref
+    , commitParents   :: [Ref]
+    , commitAuthor    :: Person
+    , commitCommitter :: Person
+    , commitEncoding  :: Maybe ByteString
+    , commitExtras    :: [CommitExtra]
+    , commitMessage   :: ByteString
+    } deriving (Show,Eq)
+
+data CommitExtra = CommitExtra
+    { commitExtraKey   :: ByteString
+    , commitExtraValue :: ByteString
+    } deriving (Show,Eq)
+
+-- | Represent a signed tag.
+data Tag = Tag
+    { tagRef        :: Ref
+    , tagObjectType :: ObjectType
+    , tagBlob       :: ByteString
+    , tagName       :: Person
+    , tagS          :: ByteString
+    } deriving (Show,Eq)
+
+-- | Delta pointing to an offset.
+data DeltaOfs = DeltaOfs Word64 Delta
+    deriving (Show,Eq)
+
+-- | Delta pointing to a ref.
+data DeltaRef = DeltaRef Ref Delta
+    deriving (Show,Eq)
diff --git a/Data/Git/WorkTree.hs b/Data/Git/WorkTree.hs
new file mode 100644
--- /dev/null
+++ b/Data/Git/WorkTree.hs
@@ -0,0 +1,151 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+-- |
+-- Module      : Data.Git.WorkTree
+-- License     : BSD-style
+-- Maintainer  : Vincent Hanquez <vincent@snarc.org>
+-- Stability   : experimental
+-- Portability : unix
+--
+-- a load-on-demand, write-on-demand working tree.
+--
+module Data.Git.WorkTree
+    ( WorkTree
+    , EntType(..)
+    -- * Create new work trees
+    , workTreeNew
+    , workTreeFrom
+    -- * Modifications methods
+    , workTreeDelete
+    , workTreeSet
+    , workTreeFlush
+    ) where
+
+import Data.Git.Ref
+import Data.Git.Types
+import Data.Git.Storage.Object
+import Data.Git.Storage
+import Data.Git.Repository
+
+--import qualified Data.ByteString as B
+
+import qualified Data.Map as M
+
+import Control.Monad
+import Control.Concurrent.MVar
+
+type Dir = M.Map EntName (ModePerm, TreeSt)
+type TreeVar = MVar Dir
+data TreeSt = TreeRef Ref | TreeLoaded TreeVar
+type WorkTree = MVar TreeSt
+
+data EntType = EntDirectory | EntFile | EntExecutable
+    deriving (Show,Eq)
+
+-- | Create a new worktree
+workTreeNew :: IO WorkTree
+workTreeNew = newMVar M.empty >>= newMVar . TreeLoaded
+
+-- | Create a worktree from a tree reference.
+workTreeFrom :: Ref -> IO WorkTree
+workTreeFrom ref = newMVar (TreeRef ref)
+
+-- | delete a path from a working tree
+--
+-- if the path doesn't exist, no error is raised
+workTreeDelete :: Git -> WorkTree -> EntPath -> IO ()
+workTreeDelete git wt path = diveFromRoot git wt path dive
+  where dive _          []     = error "internal error: delete: empty dive"
+        dive varCurrent [file] = modifyMVar_ varCurrent (return . M.delete file)
+        dive varCurrent (x:xs) = do
+            evarChild <- loadOrGetTree git x varCurrent $ \m -> return (m, Right ())
+            case evarChild of
+                Left varChild -> dive varChild xs
+                Right ()      -> return ()
+
+-- | Set a file in this working tree to a specific ref.
+--
+-- The ref should point to a valid blob or tree object, and
+-- it's safer to write the referenced tree or blob object first.
+workTreeSet :: Git -> WorkTree -> EntPath -> (EntType, Ref) -> IO ()
+workTreeSet git wt path (entType, entRef) = diveFromRoot git wt path dive
+  where dive :: TreeVar -> EntPath -> IO ()
+        dive _          []     = error "internal error: set: empty dive"
+        dive varCurrent [file] = modifyMVar_ varCurrent (return . M.insert file (entTypeToPerm entType, TreeRef entRef))
+        dive varCurrent (x:xs) = do
+            evarChild <- loadOrGetTree git x varCurrent $ \m -> do
+                            -- create an empty tree
+                            v <- newMVar M.empty
+                            return (M.insert x (entTypeToPerm EntDirectory, TreeLoaded v) m, Left v)
+            case evarChild of
+                Left varChild -> dive varChild xs
+                Right ()      -> return ()
+
+{-
+workTreeFlushAt :: Git -> WorkTree -> EntPath -> IO ()
+workTreeFlushAt git wt path = do
+    undefined
+-}
+
+-- | Flush the worktree by creating all the necessary trees in the git store
+-- and return the root ref of the work tree.
+workTreeFlush :: Git -> WorkTree -> IO Ref
+workTreeFlush git wt = do
+    -- write all the trees that need to be written
+    -- switch to modifyMVar
+    wtVal <- takeMVar wt
+    case wtVal of
+        TreeRef ref    -> putMVar wt wtVal >> return ref
+        TreeLoaded var -> do
+            ref <- writeTreeRecursively (TreeLoaded var)
+            putMVar wt $ TreeRef ref
+            return ref
+  where writeTreeRecursively (TreeRef ref) = return ref
+        writeTreeRecursively (TreeLoaded var) = do
+            c <- readMVar var
+            ents <- forM (M.toList c) $ \(bs, (mperm, entSt)) -> do
+                        ref <- writeTreeRecursively entSt
+                        return (mperm, bs, ref)
+            setTree ents
+
+        setTree ents = setObject git (toObject $ Tree ents)
+
+----- helpers -----
+
+loadTreeVar :: Git -> Ref -> IO TreeVar
+loadTreeVar git treeRef = do
+    (Tree ents) <- getTree git treeRef
+    let t = foldr (\(m,b,r) acc -> M.insert b (m,TreeRef r) acc) M.empty ents
+    newMVar t
+
+entTypeToPerm :: EntType -> ModePerm
+entTypeToPerm EntDirectory  = ModePerm 0o040000
+entTypeToPerm EntExecutable = ModePerm 0o100755 
+entTypeToPerm EntFile       = ModePerm 0o100644
+
+loadOrGetTree :: Git -> EntName -> TreeVar -> (Dir -> IO (Dir, Either TreeVar a)) -> IO (Either TreeVar a)
+loadOrGetTree git x varCurrent onMissing =
+    modifyMVar varCurrent $ \m -> do
+        case M.lookup x m of
+            Nothing          -> onMissing m
+            Just (_, treeSt) -> -- check perm to see if it is a directory
+                case treeSt of
+                    TreeRef ref -> do
+                        -- replace the ref by a loaded tree
+                        var <- loadTreeVar git ref
+                        return (M.adjust (\(perm,_) -> (perm, TreeLoaded var)) x m, Left var)
+                    TreeLoaded var -> return (m, Left var)
+
+diveFromRoot :: Git -> WorkTree -> EntPath
+             -> (TreeVar -> EntPath -> IO ())
+             -> IO () 
+diveFromRoot git wt path dive
+    | path == [] = return ()
+    | otherwise    = do
+        -- switch to modifyMVar
+        wtVal   <- takeMVar wt
+        current <- case wtVal of
+                    TreeLoaded var -> return var
+                    TreeRef ref      -> loadTreeVar git ref
+        putMVar wt $ TreeLoaded current
+        dive current path
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2010-2016 Vincent Hanquez <vincent@snarc.org>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+3. Neither the name of the author nor the names of his contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,19 @@
+git
+===
+
+[![Build Status](https://travis-ci.org/vincenthz/hs-git.png?branch=master)](https://travis-ci.org/vincenthz/hs-git)
+[![BSD](http://b.repl.ca/v1/license-BSD-blue.png)](http://en.wikipedia.org/wiki/BSD_licenses)
+[![Haskell](http://b.repl.ca/v1/language-haskell-lightgrey.png)](http://haskell.org)
+
+git is a reimplementation of git storage and protocol in pure haskell.
+
+what it does do:
+
+* read loose objects, and packed objects.
+* write new loose objects
+* git like operations available: commit, cat-file, verify-pack, rev-list, ls-tree.
+
+what is doesn't do:
+
+* reimplement the whole of git.
+* checkout's index reading/writing, fetching, merging, diffing.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/git.cabal b/git.cabal
new file mode 100644
--- /dev/null
+++ b/git.cabal
@@ -0,0 +1,108 @@
+Name:                git
+Version:             0.1
+Synopsis:            Git operations in haskell
+Description:
+    .
+    An haskell implementation of git storage operations, allowing users
+    to manipulate git repositories (read and write).
+    .
+    This implementation is fully interoperable with the main C implementation.
+    .
+    This is stricly only manipulating the git store (what's inside the .git directory),
+    and doesn't do anything with the index or your working directory files.
+    .
+License:             BSD3
+License-file:        LICENSE
+Copyright:           Vincent Hanquez <vincent@snarc.org>
+Author:              Vincent Hanquez <vincent@snarc.org>
+Maintainer:          Vincent Hanquez <vincent@snarc.org>
+Category:            Development
+Stability:           experimental
+Build-Type:          Simple
+Homepage:            https://github.com/vincenthz/hit
+tested-with:         GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.1
+Cabal-Version:       >=1.8
+data-files:          README.md
+extra-source-files:  Tests/*.hs
+
+Flag executable
+  Description:       Build the executable
+  Default:           False
+
+Flag debug
+  Description:       Add some debugging options
+  Default:           False
+
+Library
+  Build-Depends:     base >= 4 && < 5
+                   , mtl
+                   , bytestring >= 0.9
+                   , byteable
+                   , containers
+                   , memory >= 0.13
+                   , cryptonite
+                   , vector
+                   , random
+                   , zlib
+                   , zlib-bindings >= 0.1 && < 0.2
+                   , hourglass >= 0.2
+                   , unix-compat
+                   , utf8-string
+                   , patience
+                   -- to remove
+                   , system-filepath
+                   , system-fileio
+  Exposed-modules:   Data.Git
+                     Data.Git.Monad
+                     Data.Git.Types
+                     Data.Git.Storage
+                     Data.Git.Storage.PackIndex
+                     Data.Git.Storage.Pack
+                     Data.Git.Storage.Object
+                     Data.Git.Storage.Loose
+                     Data.Git.Named
+                     Data.Git.Delta
+                     Data.Git.Ref
+                     Data.Git.Revision
+                     Data.Git.Repository
+                     Data.Git.Diff
+  Other-modules:     Data.Git.Internal
+                     Data.Git.Imports
+                     Data.Git.OS
+                     Data.Git.Config
+                     Data.Git.Storage.FileReader
+                     Data.Git.Storage.FileWriter
+                     Data.Git.Storage.CacheFile
+                     Data.Git.Path
+                     Data.Git.Parser
+                     Data.Git.WorkTree
+  ghc-options:       -Wall -fno-warn-unused-imports
+ -- -fno-warn-missing-signatures
+
+
+Test-Suite test-unit
+  type:              exitcode-stdio-1.0
+  hs-source-dirs:    tests
+  Main-Is:           Tests.hs
+  Build-depends:     base >= 3 && < 7
+                   , bytestring
+                   , tasty
+                   , tasty-quickcheck
+                   , hourglass
+                   , git
+
+Test-Suite test-repository
+  type:              exitcode-stdio-1.0
+  hs-source-dirs:    tests
+  Main-Is:           Repo.hs
+  Build-depends:     base >= 3 && < 7
+                   , bytestring
+                   , tasty
+                   , tasty-quickcheck
+                   , hourglass
+                   , bytedump >= 1.0
+                   , git
+
+source-repository head
+  type: git
+  location: https://github.com/vincenthz/hs-git
diff --git a/tests/Monad.hs b/tests/Monad.hs
new file mode 100644
--- /dev/null
+++ b/tests/Monad.hs
@@ -0,0 +1,63 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Monad
+    ( testGitMonadLocal
+    ) where
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad
+
+import Data.Git.Monad
+import Data.Git.Types (GitTime(..))
+import System.Exit
+import qualified System.Hourglass as T
+
+testBranch :: RefName
+testBranch = "test/not/push"
+
+catchAll :: IO (Either String a) -> IO ()
+catchAll f = do
+    r <- catchAll' f
+    case r of
+        Left  err        -> failWith $ show err
+        Right (Left err) -> failWith err
+        Right (Right _)  -> putStrLn " test/git/monad [OK]"
+  where
+    catchAll' :: IO a -> IO (Either SomeException a)
+    catchAll' f = try f
+
+    failWith :: String -> IO ()
+    failWith msg = do
+        putStrLn " test/git/monad [FAILED]"
+        putStrLn $ " - " ++ msg
+        exitFailure
+
+testGitMonadLocal :: IO ()
+testGitMonadLocal = catchAll (withCurrentRepo testGitMonad)
+
+timeCurrentGit :: GitM GitTime
+timeCurrentGit = liftGit $ GitTime 
+    <$> T.timeCurrent
+    <*> T.timezoneCurrent
+
+step :: String -> GitM ()
+step = liftGit . putStrLn
+
+testGitMonad :: GitM ()
+testGitMonad = do
+    t <- timeCurrentGit
+    let person = Person
+            { personName  = "Hit Test Machinery"
+            , personEmail = "hit@snarc.org"
+            , personTime  = t
+            }
+    withBranch person testBranch True (return ()) $ \isFirstCommit -> case isFirstCommit of
+        Nothing -> setMessage "Initial commit"
+        Just _  -> setMessage "add new commit"
+    step " + new branch created"
+    withCommit testBranch $ do
+        author <- getAuthor
+        when (t /= personTime author)
+            $ fail "master's commit is not the last commit performed"
+    step " + branch has been verified"
diff --git a/tests/Repo.hs b/tests/Repo.hs
new file mode 100644
--- /dev/null
+++ b/tests/Repo.hs
@@ -0,0 +1,63 @@
+import Test.Tasty
+import Test.Tasty.QuickCheck
+
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString as B
+
+import Control.Applicative
+import Control.Monad
+
+import Data.Git.Storage.Object
+import Data.Git.Storage.Loose
+import Data.Git.Storage
+import Data.Git.Ref
+import Data.Git.Types
+import Data.Git.Repository
+
+import Data.Maybe
+
+import Text.Bytedump
+import System.Exit
+
+import Monad
+
+onLocalRepo f = do
+    fpath <- findRepoMaybe
+    case fpath of
+        Nothing -> putStrLn "cannot run this test without repository. clone the original repository for test"
+        Just _  -> withCurrentRepo f
+
+doLocalMarshallEq git = do
+     prefixes <- looseEnumeratePrefixes (gitRepoPath git)
+     forM prefixes $ \prefix -> do
+         refs <- looseEnumerateWithPrefix (gitRepoPath git) prefix
+         forM refs $ \ref -> do
+             raw <- looseReadRaw (gitRepoPath git) ref
+             obj <- looseRead (gitRepoPath git) ref
+             let content = looseMarshall obj
+             let raw2 = looseUnmarshallRaw content
+             let hashed = hashLBS content
+             if ref /= hashed
+                  then return $ Just (ref, hashed, raw, raw2)
+                  else return Nothing
+
+printDiff (actualRef, gotRef, (actualHeader, actualRaw), (gotHeader, gotRaw)) = do
+    putStrLn "=========== difference found"
+    putStrLn ("ref expected: " ++ show actualRef)
+    putStrLn ("ref got     : " ++ show gotRef)
+    putStrLn ("header expected: " ++ show actualHeader)
+    putStrLn ("header got     : " ++ show gotHeader)
+    putStrLn "raw diff:"
+    putStrLn $ dumpDiffLBS actualRaw gotRaw
+
+printLocalMarshallError l
+    | null l    = putStrLn "local marshall:   [OK]"
+    | otherwise = putStrLn ("local marshall: [" ++ show (length l) ++ " errors]")
+               >> mapM_ printDiff l
+               >> exitFailure
+
+main = do
+    onLocalRepo $ \git -> do
+        doLocalMarshallEq git >>= printLocalMarshallError . catMaybes . concat
+        return ()
+    testGitMonadLocal
diff --git a/tests/Tests.hs b/tests/Tests.hs
new file mode 100644
--- /dev/null
+++ b/tests/Tests.hs
@@ -0,0 +1,132 @@
+import Test.Tasty.QuickCheck
+import Test.Tasty
+
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString as B
+
+import Control.Applicative
+import Control.Monad
+
+import Data.Git.Storage.Object
+import Data.Git.Storage.Loose
+import Data.Git.Ref
+import Data.Git.Revision
+import Data.Git.Types
+import Data.Hourglass
+
+import Data.Maybe
+
+-- for arbitrary instance to generate only data that are writable
+-- to disk. i.e. no deltas.
+data ObjNoDelta = ObjNoDelta Object
+    deriving (Eq)
+
+instance Show ObjNoDelta where
+    show (ObjNoDelta o) = show o
+
+arbitraryBS size = B.pack . map fromIntegral <$> replicateM size (choose (0,255) :: Gen Int)
+arbitraryBSno0 size = B.pack . map fromIntegral <$> replicateM size (choose (1,255) :: Gen Int)
+arbitraryBSasciiNoSpace size = B.pack . map fromIntegral <$> replicateM size (choose (0x21,0x7f) :: Gen Int)
+arbitraryBSascii size = B.pack . map fromIntegral <$> replicateM size (choose (0x20,0x7f) :: Gen Int)
+arbitraryBSnoangle size = B.pack . map fromIntegral <$> replicateM size (choose (0x40,0x7f) :: Gen Int)
+
+arbitraryEntname size = entName . B.pack . map fromIntegral <$> replicateM size range
+  where range :: Gen Int
+        range = oneof [ choose (0x21, 0x2e) -- remove 0x2f (slash)
+                      , choose (0x30, 0x7f)
+                      ]
+
+instance Arbitrary Ref where
+    arbitrary = fromBinary <$> arbitraryBS 20
+
+arbitraryMsg = arbitraryBSno0 1
+arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 40
+
+arbitraryRefList :: Gen [Ref]
+arbitraryRefList = replicateM 2 arbitrary
+
+arbitraryEnt :: Gen TreeEnt
+arbitraryEnt = liftM3 (,,) arbitrary (arbitraryEntname 23) arbitrary
+arbitraryEnts = choose (1,2) >>= \i -> replicateM i arbitraryEnt
+
+instance Arbitrary TimezoneOffset where
+    arbitrary = TimezoneOffset <$> choose (-11*60, 12*60)
+instance Arbitrary Elapsed where
+    arbitrary = Elapsed . Seconds <$> choose (0,2^32-1)
+instance Arbitrary GitTime where
+    arbitrary = GitTime <$> arbitrary <*> arbitrary
+instance Arbitrary ModePerm where
+    arbitrary = ModePerm <$> elements [ 0o644, 0o664, 0o755, 0 ]
+instance Arbitrary RevModifier where
+    arbitrary = oneof
+        [ RevModParent . getPositive <$> arbitrary
+        , RevModParentFirstN . getPositive <$> arbitrary
+        , RevModAtType <$> arbitraryType
+        , RevModAtDate <$> arbitraryDate
+        --, RevModAtN . getPositive <$> arbitrary
+        ]
+
+arbitraryDate = elements ["yesterday","29-Jan-1982","5 days ago"]
+arbitraryType = elements ["commit","tree"]
+
+instance Arbitrary Revision where
+    arbitrary = do
+        s   <- choose (1,40) >>= flip replicateM (elements ['a'..'z'])
+        rms <- choose (1,4) >>= flip replicateM arbitrary
+        return $ Revision s rms
+
+arbitraryName = liftM3 Person (arbitraryBSnoangle 16)
+                              (arbitraryBSnoangle 16)
+                              arbitrary
+
+arbitraryObjTypeNoDelta = oneof [return TypeTree,return TypeBlob,return TypeCommit,return TypeTag]
+
+arbitrarySmallList = frequency [ (2, return []), (1, resize 3 arbitrary) ]
+
+instance Arbitrary Commit where
+    arbitrary = Commit <$> arbitrary <*> arbitraryRefList <*> arbitraryName <*> arbitraryName <*> return Nothing <*> arbitrarySmallList <*> arbitraryMsg
+
+instance Arbitrary CommitExtra where
+    arbitrary = CommitExtra <$> arbitraryBSasciiNoSpace 80 <*> arbitraryMsg
+
+instance Arbitrary Tree where
+    arbitrary = Tree <$> arbitraryEnts
+
+instance Arbitrary Blob where
+    arbitrary = Blob <$> arbitraryLazy
+
+instance Arbitrary Tag where
+    arbitrary = Tag <$> arbitrary <*> arbitraryObjTypeNoDelta <*> arbitraryBSascii 20 <*> arbitraryName <*> arbitraryMsg
+
+instance Arbitrary ObjNoDelta where
+    arbitrary = ObjNoDelta <$> oneof
+        [ toObject <$> (arbitrary :: Gen Commit)
+        , toObject <$> (arbitrary :: Gen Tree)
+        , toObject <$> (arbitrary :: Gen Blob)
+        , toObject <$> (arbitrary :: Gen Tag)
+        ]
+
+prop_object_marshalling_id (ObjNoDelta obj) = obj `assertEq` (looseUnmarshall $ looseMarshall obj)
+    where assertEq a b
+            | show a == show b    = True
+            | otherwise = error ("not equal:\n"  ++ show a ++ "\ngot: " ++ show b)
+
+refTests =
+    [ testProperty "hexadecimal" (marshEqual (fromHex . toHex))
+    , testProperty "binary" (marshEqual (fromBinary . toBinary))
+    , testProperty "ref" $ marshEqual (fromString . show :: Revision -> Revision)
+    ]
+    where
+        marshEqual t ref = ref `assertEq` t ref
+        assertEq a b
+            | a == b    = True
+            | otherwise = error ("expecting: " ++ show a ++ " got: " ++ show b)
+
+objTests =
+    [ testProperty "unmarshall.marshall==id" prop_object_marshalling_id
+    ]
+
+main = defaultMain $ testGroup "hit"
+    [ testGroup "ref marshalling" refTests
+    , testGroup "object marshalling" objTests
+    ]
