In previous articles, we learned about Git, why version control systems were created, and the problems they solve. In this article, we will take a closer look at the .git folder that is created when initializing a local repository in a new codebase.
Creating a New Git Repository
We begin our Git journey with the command git init. After running this command, we receive confirmation that an empty Git repository has been created.
Initialized empty Git repository in /Users/manmeet/Developer/cohort/inside-git/.git/
This command also creates a hidden .git folder inside our current project folder. To see what’s inside this .git folder, we can use the tree command. We get the following structure:
.git
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ └── ... (other sample hooks)
├── info
│ └── exclude
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
Alright, there’s a lot in this folder. Let’s go through each file and directory one by one to understand what they are.
- config: The config file holds all the settings for the current repository, like the author’s information, filemode, and other basic information.
- description: The description file is used only by the GitWeb UI program to display the description of the repository. By default, it holds the content “Unnamed repository…” and can usually be ignored.
- HEAD: The HEAD file contains the reference to the currently used branch, such as
mainormaster. If you look through the contents, it usually containsref: refs/heads/main, which points to therefsfolder that we will cover later on. - hooks: The hooks folder contains multiple scripts that can be used to perform certain steps automatically before or after Git has performed some operation. The files ending in
.sampleare disabled by default. - info/exclude: The exclude file is located at
/info/excludeand includes the files that you don’t want to be tracked by Git. However, what makes it different from.gitignoreis that this is completely local and is not versioned or pushed to any remote repository. A real-life use case would be if one developer uses a specific tool for testing and wants those specific files excluded from their system only, without affecting the rest of the team. - objects: The objects folder contains the data about the files, commits, and other information in the form of Git objects. We will cover this further in the blog.
- refs: The refs folder, as the name implies, stores references or pointers to the branches and tags. The folder
/refs/headscontains the reference to the latest commits stored on the branch, and/refs/tagsstores the references to the latest commit related to that tag.
How Git Works Under the Hood
Let’s create a file hello.txt and create a commit to the main branch. The commands to perform this action are:
echo "hello world" > hello.txt
git add hello.txt
git commit -m "added hello.txt file"
The commands would create a new file hello.txt with the content “hello world,” add it to the staging area, and then commit it to the main branch with the message “added hello.txt file.” The output of the commit message is:
[main (root-commit) 9ef19b4] added hello.txt file
1 file changed, 1 insertion(+)
create mode 100644 hello.txt
We can see the history using git log. The output shows the author, time, message, and a unique SHA-1 hash assigned to the commit.
commit 9ef19b4705d268cbdf5906b6e1bdb73f88dc622b (HEAD -> main)
Author: Manmeet <email_address_here>
Date: Wed Jan 21 00:28:44 2026 +0530
added hello.txt file
Let’s use the tree command again on that hidden .git folder and see what has changed.
.git
├── COMMIT_EDITMSG
├── config
├── ...
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── main
├── objects
│ ├── 3b
│ │ └── 18e512dba79e4c8300dd08aeb37f8e728b8dad
│ ├── 68
│ │ └── aba62e560c0ebc3396e8ae9335232cd93a3f60
│ ├── 9e
│ │ └── f19b4705d268cbdf5906b6e1bdb73f88dc622b
...
└── refs
├── heads
│ └── main
└── tags
Observations
- The objects folder now has multiple subfolders that start with the first two characters of the commit hash. In our commit, the hash was
9ef19b4..., and we can clearly see a folder starting with9ein the objects folder. - The refs folder has also changed, with
/refs/headsnow containing amainfile. If we look at the contents of this/refs/heads/mainfile, we find the hash of our latest commit9ef19b4.... It’s the same hash that thegit logcommand provided us. - A new file
COMMIT_EDITMSGcontains the last commit message.
Objects Folder
So, now we know that the HEAD file contains a pointer to the branch with the latest commit, which in our case is ref: refs/heads/main. This main file contains the hash of the latest commit, and that commit can be found in the objects folder.
Let’s go to the .git/objects folder and list all the files and folders in this directory.
cd .git/objects
ls
# outputs: 3b 68 9e info pack
The output of the command above shows several folders with only two characters. These two characters are the first two characters of the hash from the latest commit.
- Latest Commit Hash:
9ef19b4705d268cbdf5906b6e1bdb73f88dc622b - First two characters:
9e - Remaining:
f19b4705d268cbdf5906b6e1bdb73f88dc622b
So, if we try to go to the folder 9e and print the content of the file f19b4... we get an output that is not easily understandable by us.
cd 9e
ls
cat f19b4705d268cbdf5906b6e1bdb73f88dc622b
# Output: x??A? @Ѯ=???X'?Rz??Nj@c1ho?,{?n...
Just look at the output above. Can you figure out what it means? Yeah, neither can I. This “garbage” text appears because Git compresses the files using zlib to save space. However, luckily, we have a tool called git cat-file that can help us understand the information stored in this file.
Let’s move outside the .git folder and run the command git cat-file -p <latest_commit_hash>:
git cat-file -p 9ef19b4705d268cbdf5906b6e1bdb73f88dc622b
Output:
tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
author Manmeet <email_address_here> 1768935524 +0530
committer Manmeet <email_address_here> 1768935524 +0530
added hello.txt file
This file contains the exact details of the commit object, including the tree, author, committer, time, and commit message of the latest commit.
What is Tree?
We just saw that on printing the information stored in the latest commit object, there’s some other object called “tree” stored here. Let’s try to use our cat-file command on this git object and see the output.
git cat-file -p 68aba62e560c0ebc3396e8ae9335232cd93a3f60
Output:
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txt
From the output, we can see that the tree object contains yet another hash. Now let’s try to cat-file this new hash that was stored by the tree object.
git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad
Output:
hello world
This new hash contained the content stored in our hello.txt file, which was “hello world”. This is the Blob Object. It contains the actual content of our file.
Hence, a tree object acts like a directory. It maps filenames (like hello.txt) to Blob objects.
Linking Commits: The parent Field
So far, we have only looked at a single commit. But Git is all about history. How does Git know that one commit comes after another?
Let’s modify our file and create a second commit to see what happens.
echo "hello again" >> hello.txt
git add hello.txt
git commit -m "updated hello.txt"
Now, let’s check the log to get our new commit hash.
git log --oneline
# Output:
# 8ad32c1 (HEAD -> main) updated hello.txt
# 9ef19b4 added hello.txt file
We have a new commit with the hash starting with 8ad32c. Let’s use our trusty git cat-file command to look inside this new commit object.
git cat-file -p 8ad32c1
Output:
tree 42091e6d12345...
parent 9ef19b4705d268cbdf5906b6e1bdb73f88dc622b
author Manmeet <email@example.com> 1768936000 +0530
committer Manmeet <email@example.com> 1768936000 +0530
updated hello.txt
Observation: Notice that there is a new line that wasn’t there before: parent.
- The
parentfield contains the hash of our previous commit (9ef19b4...). - This “parent” pointer is how Git connects commits together.
If we made a third commit, it would point to 8ad32c as its parent. This chain of parents allows Git to traverse backward through history, from the current state all the way back to the very first commit.
Btw for anyone curious the data structure used here is Linked List.
Conclusion
By following the breadcrumbs, we uncovered how Git stores data:
- Commit: Stores the metadata (author, time) and points to a Tree.
- Tree: Stores the directory structure and filenames, pointing to Blobs.
- Blob: Stores the raw file content.
Now you know what the hidden .git folder contains and how Git organizes and stores data for efficient project management.