In [1]:
:set -XOverloadedStrings
import Data.Maybe (fromJust)
import qualified Data.Set as S
import Prelude hiding ((^^))
import Duffer
import Duffer.Loose
import Duffer.Loose.Objects
import Duffer.WithRepo
import Duffer.Unified
duffer = withRepo "../.git"
resolveRef' = fmap fromJust . resolveRef
readObject' = fmap fromJust . readObject
Let's start with a raw representation of the most recent commit:
In [2]:
:!git show --format=raw -s
I'm currently on the master branch, so another way to get to this object is as follows:
In [3]:
duffer (resolveRef' "refs/heads/master")
A commit refers to a tree, which is git's way of storing a directory. An example tree looks like
We can view the pretty-printed contents of a git object with cat-file -p. Each commit has a tree associated with it which represents a directory, in this case the root project folder.
In [4]:
:!git cat-file -p master^{tree}
Again, we can obtain almost identical (modulo formatting) output with duffer:
In [5]:
duffer $ do
GitCommit master <- resolveRef' "refs/heads/master"
let tree = commitTreeRef master
readObject' tree
git implements a giant hashtable on the filesystem using SHA1 as the hashing function. It stores all the past files and directory listings as zlib-compressed text files (with a header denoting object type and length) under .git/objects as follows:
zlib-compress the content..git/objects where the content will be stored.For example, a decompressed commit looks like:
In [6]:
:!cat ../.git/objects/4b/d9b179bb166b85e3e889f9f263f1b5a26f3e34 | zlib-flate -uncompress
In [7]:
duffer $ readObject' "4bd9b179bb166b85e3e889f9f263f1b5a26f3e34"
In [8]:
:!git branch
In [9]:
duffer $ do
current <- resolveRef' "refs/heads/master"
parent <- fromJust <$> current ^^ 1
fromJust <$> parent ~~ 1
As mentioned previously, the hash of a git object uniquely identifies it in the giant hashtable that is git
In [10]:
tree <- duffer $ readObject' "a28aded05daa52ff5d0c77cd6186b1ce0faf7c8c"
hash tree
git refers to files as blobs.
In [11]:
duffer $ readObject' "b75f4c9dbe3b61cacba052f23461834468832e41"
The last type of git object is a tag, which gives a name to another git object.
In [12]:
duffer $ readObject' "d4b1e0343313ab60688cf0ddfa8ae5d8fe60ec23"
duffer is pretty great at reading git repositories, but that's not all you can do with it. You can also add content to a git repository with it:
In [13]:
import Data.ByteString.UTF8 (fromString, toString)
blob = GitBlob $ Blob (fromString "hello world")
duffer $ writeLooseObject blob
In [14]:
:!git cat-file -p 95d09f2b10159347eece71399a7e2e907ea3df4f
In [15]:
:!git branch
In [16]:
currentCommitObject = resolveRef' "refs/heads/master"
duffer $ currentCommitObject >>= \commit -> updateRef "refs/heads/new-branch" commit
In [17]:
:!git branch
In [18]:
rootTreeObject <- duffer $ currentCommitObject >>= (\(GitCommit c) -> return $ commitTreeRef c) >>= readObject'
rootTreeObject
In [19]:
GitTree rootTree = rootTreeObject
newFile = TreeEntry Regular "new-file" "95d09f2b10159347eece71399a7e2e907ea3df4f"
duffer $ do
let entries = treeEntries rootTree
let newEntries = S.insert newFile entries
newTree <- writeLooseObject . GitTree $ Tree newEntries
let me = PersonTime "Vaibhav Sagar" "vaibhavsagar@gmail.com" "1461156164" "+1000"
let newCommit = GitCommit $ Commit newTree ["d76238fed6c656183a4d4dcf287217a061043869"] me me Nothing "New commit."
newHead <- writeLooseObject newCommit
updateRef "refs/heads/new-branch" newCommit
In [20]:
newTree = duffer $ resolveRef' "refs/heads/new-branch" >>= (\(GitCommit c) -> return $ commitTreeRef c) >>= readObject'
newTree