Skip to content
</MS>

Blog

Exploring the .git Folder: What You Need to Know

Discover the inner workings of the `.git` folder, essential for understanding Git's file storage, commit history, and version control

Jan 21, 2026 · 7 min read

Exploring the .git Folder: What You Need to Know

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 main or master. If you look through the contents, it usually contains ref: refs/heads/main, which points to the refs folder 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 .sample are disabled by default.
  • info/exclude: The exclude file is located at /info/exclude and includes the files that you don’t want to be tracked by Git. However, what makes it different from .gitignore is 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/heads contains the reference to the latest commits stored on the branch, and /refs/tags stores 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

  1. 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 with 9e in the objects folder.
  2. The refs folder has also changed, with /refs/heads now containing a main file. If we look at the contents of this /refs/heads/main file, we find the hash of our latest commit 9ef19b4.... It’s the same hash that the git log command provided us.
  3. A new file COMMIT_EDITMSG contains 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 parent field 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:

  1. Commit: Stores the metadata (author, time) and points to a Tree.
  2. Tree: Stores the directory structure and filenames, pointing to Blobs.
  3. 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.