Low Level Git

Low level git commands are rarely used in the day-to-day use of git but they are a great way to introduce how git works under the hood. These notes focus on some lower level git concepts. But first, git can store many files in a repository, in fact it’s 2160 files which is obscenely large and the chances of a hash collision between files is 1/2320 which is obscenely small.

Create an empty repository by running the following commands in a suitable directory.

mkdir first-project
cd first-project
git init

This will create a hidden .git directory. Note that the repository is empty even if we run git init in an existing directory with files already in it. The structure we get from the above commands is:

. 
└── .git 
   ├── branches 
   ├── config 
   ├── description 
   ├── HEAD 
   ├── hooks 
   │   ├── applypatch-msg.sample 
   │   ├── commit-msg.sample 
   │   ├── fsmonitor-watchman.sample 
   │   ├── post-update.sample 
   │   ├── pre-applypatch.sample 
   │   ├── pre-commit.sample 
   │   ├── pre-merge-commit.sample 
   │   ├── prepare-commit-msg.sample 
   │   ├── pre-push.sample 
   │   ├── pre-rebase.sample 
   │   ├── pre-receive.sample 
   │   ├── push-to-checkout.sample 
   │   ├── sendemail-validate.sample 
   │   └── update.sample 
   ├── info 
   │   └── exclude 
   ├── objects 
   │   ├── info 
   │   └── pack 
   └── refs 
       ├── heads 
       └── tags 

11 directories, 18 files

We will mostly be focusing on the objects directory. Git creates folders and files in this directory based on the sha1 hash of your files. The first two characters are the folder name and the remainder the file name with in the folder. Git can have up to 256 data directories in the objects directory. Git creates 4 types of file in there, blobs, trees, commits, and annotated tags. Never make manual changes in the .git directory, it must be managed by git.

In blob files, git stores files, text, images, videos, and any other kind of file.  The tree object stores directories. A tree can have other trees, blobs, or a combination of both. Trees are where git actually stores your file names. Commits store a point in time, a version of our project and finally annotated tags store a text pointer to a specific commit. These four objects allow git to store files and track changes, along with tagging.

We can create a blob by running the following command:

echo "Hello, Git" | git hash-object --stdin -w

Then if we examine our directory structure we see:

    │   └── exclude 
   ├── objects 
   │   ├── b7 
   │   │   └── aec520dec0a7516c18eb4c68b64ae1eb9b5a5e 
   │   ├── info 
   │   └── pack 
   └── refs

Notice the b7 folder and the file. Together the names of the folder and file make up the sha1 hash of the file.If we entered exactly the same text, we will have the same file names. Also notice that no files were created in our project directory.

We can examine our git blob using the git cat-file command:

git cat-file -p b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e
git cat-file -s b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e
git cat-file -t b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e

The -p flag shows us the contents of the object, the -s flag shows us the size of the object, and the -t flag shows us the type of the object.

Now let’s create a new blob, but this time from a file. In a directory one level up from our project (we don’t want the file in our project directory) run the following commands

echo "Second file in Git repository" > new-file.txt
cd first-project
git hash-object ../new-file.txt -w

If we examine our file structure using tree -a we see another directory and file under objects:

    │   └── exclude 
    ├── objects 
    │   ├── 44 
    │   │   └── 00aae52a27341314f423095846b1f215a7cf08 
    │   ├── b7 
    │   │   └── aec520dec0a7516c18eb4c68b64ae1eb9b5a5e 
    │   ├── info
 

Exploring the new file with git cat-file, we see the appropriate data. Using git cat-file, it is noticeable that blobs don’t store the file names. This means that two files with the same contents will have the same blob. The size and type of the object is stored directly in the object. The format of the object file object type size null Contents so for our first file “blob 11\0Hello, Git”.

Tree objects store four data fields, the permissions, the type of object, the sha1 hash of the object and the file name.

There are six possible permissions in a git tree, but usually we will see the first two

040000 Directory
100644 Regular non-executable file
100664 Regular non-executable group-writable file
100755 Regular executable file
120000 Symbolic link
160000 Git link“

 

Using the low level git command git mktree we can create a tree object. Outside of our project directory, create a file with the following contents:

100644 blob b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e    file1.txt
100644 blob 4400aae52a27341314f423095846b1f215a7cf08      file2.txt

Where after the hash, there is a tab. Then to create the tree, run the following command:

cat ../temp-tree.txt| git mktree

replacing the file name as appropriate. We will see that the objects now contains three folders and files:

   │   └── exclude 
   ├── objects 
   │   ├── 3e 
   │   │   └── 522d4bbea1721ab73b9bf6892187f03a13cb1c 
   │   ├── 44 
   │   │   └── 00aae52a27341314f423095846b1f215a7cf08 
   │   ├── b7 
   │   │   └── aec520dec0a7516c18eb4c68b64ae1eb9b5a5e 
   │   ├── info 

We can examine this new object using git cat-file and we will see that it is a tree object.

A git repository has three areas for files, the working directory, the staging area or index, and the git repository itself. So far we have created and examined objects directly in the git repository. Note that putting files in the staging area is a mandatory step when moving between the repository and working directory in either direction.

To checkout the files in the repository to the staging area use the git read-tree command:

git read-tree 3e522

We can examine the staging area using the git ls-files command:

git ls-files -s

Which shows the files

100644 b7aec520dec0a7516c18eb4c68b64ae1eb9b5a5e 0       file1.txt  
100644 4400aae52a27341314f423095846b1f215a7cf08 0       file2.txt

We can put the files in the staging area into the working directory by using the git read-tree command:

 

git checkout-index -a

Now we will see the files in our working directory.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.