In [1]:
:set -XOverloadedStrings

In [2]:
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as L
import Data.Attoparsec.ByteString as AB
import Data.Attoparsec.ByteString.Char8 (anyChar)
import Prelude hiding (take)
import Numeric
import Data.ByteString.UTF8 (toString, fromString)
import Data.ByteString.Base16 (encode, decode)
import Data.List (intercalate)

In [3]:
let index = B.readFile "../.git/index"

In [4]:
word8s = string . B.pack
fromBytes = foldl addByte 0
    where addByte num d = (256*num) + d

toBytes n = case divMod n 256 of
    (0, i) -> [i]
    (x, y) -> toBytes x ++ toBytes y


fromPack :: B.ByteString -> Int
fromPack = fromBytes . map (fromIntegral . toInteger) . B.unpack

toPack len num = let bytes   = toBytes num :: [Int]
                     pNum    = len - length bytes
                     padding = replicate pNum 0
                  in B.pack $ map fromIntegral (padding ++ bytes)

In [5]:
data Index = Index
    { indexVersion :: Int
    , indexEntries :: [IndexEntry]
    , indexExtra   :: B.ByteString}

data IndexEntry = IndexEntry
    { metadataChanged  :: Int
    , metadataFraction :: Int
    , dataChanged      :: Int
    , dataFraction     :: Int
    , dev              :: Int
    , ino              :: Int
    , mode             :: Int
    , uid              :: Int
    , gid              :: Int
    , fileSize         :: Int
    , sha1             :: B.ByteString
    , flags            :: Int
    , name             :: B.ByteString}
    deriving (Eq)

instance Show IndexEntry where
    show (IndexEntry mChanged mFraction dChanged dFraction dev ino mode uid gid size sha1 flags name) = intercalate " " components
        where components = (map show [mChanged, mFraction, dChanged, dFraction, dev, ino, mode, uid, gid, size]) ++ [toString sha1, show flags, toString name]   

parseIndexEntry 2 = IndexEntry
    <$> (fromPack <$> take 4)
    <*> (fromPack <$> take 4)
    <*> (fromPack <$> take 4)           
    <*> (fromPack <$> take 4)           -- dataFraction
    <*> (fromPack <$> take 4)           -- dev
    <*> (fromPack <$> take 4)           -- ino
    <*> (fromPack <$> take 4)           -- mode
    <*> (fromPack <$> take 4)           -- uid
    <*> (fromPack <$> take 4)           -- gid
    <*> (fromPack <$> take 4)           -- fileSize
    <*> (encode   <$> take 20)          -- sha1
    <*> (fromPack <$> take 2)           -- flags
    <*> (do name <- takeTill (==0) 
            count (8 - ((6 + B.length name) `mod` 8)) $ string "\NUL"
            return name)
    
parseIndexEntry _ = undefined

parseIndex = do
    string "DIRC"
    version     <- fromPack <$> (choice $ map word8s [[0,0,0,2], [0,0,0,3], [0,0,0,4]])
    noOfEntries <- fromPack <$> take 4
    entries     <- count noOfEntries $ parseIndexEntry version
    rest        <- takeByteString
    return $ Index version entries rest

parsedIndex <- either error id . parseOnly parseIndex <$> index
entries = indexEntries parsedIndex

In [6]:
unparseIndexEntry 2 entry = B.concat components
    where components = [ toPack 4 $ metadataChanged   entry
                       , toPack 4 $ metadataFraction entry
                       , toPack 4 $ dataChanged      entry
                       , toPack 4 $ dataFraction     entry
                       , toPack 4 $ dev              entry
                       , toPack 4 $ ino              entry
                       , toPack 4 $ mode             entry
                       , toPack 4 $ uid              entry
                       , toPack 4 $ gid              entry
                       , toPack 4 $ fileSize         entry
                       , fst $ decode $ sha1         entry
                       , toPack 2 $ flags            entry
                       ,            name             entry
                       , B.concat $ replicate (8 - ((6 + B.length (name entry)) `mod` 8)) "\NUL"]

In [7]:
reparse e = either error id $ parseOnly (parseIndexEntry 2) (unparseIndexEntry 2 e)
checkEqual e = reparse e == e

In [8]:
all checkEqual entries


True

In [9]:
toBytes 1460985245


[87,20,221,157]

In [10]:
map name entries


[".ci/update_pages.sh",".gitignore",".travis.yml","LICENSE","README.md","Setup.hs","duffer.cabal","notebooks/Index.ipynb","notebooks/demo.ipynb","presentation/presentation.md","src/Duffer.hs","src/Duffer/Loose.hs","src/Duffer/Loose/Objects.hs","src/Duffer/Loose/Parser.hs","src/Duffer/Pack.hs","src/Duffer/Pack/Entries.hs","src/Duffer/Pack/File.hs","src/Duffer/Pack/Parser.hs","src/Duffer/Pack/Streaming.hs","src/Duffer/Plumbing.hs","src/Duffer/Unified.hs","src/Duffer/WithRepo.hs","stack.yaml","test/Spec.hs"]