Getting started

Create plain new directory run git init

mkdir git-experience
cd git-experience

git init

The git init command creates a .git directory inside the current directory with the following structure

.git
├── config
├── description
├── FETCH_HEAD
├── HEAD
├── hooks
│  ├── applypatch-msg.sample
│  ├── commit-msg.sample
├── info
│  └── exclude
├── objects
│  ├── info
│  └── pack
└── refs
   ├── heads
   └── tags

The config file store setting of the local repository extends from the global config and overwrites the predefined value. You can config different name or emails due to each project.

git config user.name "the name you want in this project"
git config user.email "[email protected]"

# Set default branch name is "main"
git config init.defaultBranch main

Initially, the folder objects contains two empty folder info and pack

Your first commit

Create a file in the current directory

echo 'Hello, world' >| hello.txt

# Then run this command
git hash-object hello.txt

# This command return same result as above
git hash-object <(echo 'Hello, world')

The command above returns a hashed string like a5c19667710254f835085b99726e523457150e03. This hash string is identical to the file’s content; you can copy the file to another computer and run the same command, and the result is similar.

The hello.txt currently is in the working directory; run the command git add hello.txt to add it to the git staging area. Now take a look at the directory .git/objects.

.git/objects
├── a5
│  └── c19667710254f835085b99726e523457150e03
├── info
└── pack

A new file was created under file path a5/c19667710254f835085b99726e523457150e03; if you remove the forward slash - directory delimiter, you will see the path matches with the hashed string of file content.

Create the first commit

git commit -m 'First commit'

After that, the folder .git/objects may look like this (on your local, new added files may have different name & path)

.git/objects
├── 67
│  └── ac38590b37477deff534cc43c90d2e97a5d95a
├── 91
│  └── acfacade0f2304724903f60f5648a5060aa011
├── a5
│  └── c19667710254f835085b99726e523457150e03
├── info
└── pack

These git object files are different in types:

find .git/objects -type f | awk -F/ '{print $3$4}' | while read hash; do echo $hash - $(git cat-file -t $hash); done

# The result is like
# 67ac38590b37477deff534cc43c90d2e97a5d95a - tree
# a5c19667710254f835085b99726e523457150e03 - blob
# 91acfacade0f2304724903f60f5648a5060aa011 - commit

blob file is the fundamental data unit; git uses blobs to manage file versions, and the whole system is about blob management. Reading blob file returns the content of file hello.txt when you open it:

git cat-file blob a5c19667710254f835085b99726e523457150e03

# or

git show a5c19667710254f835085b99726e523457150e03

blob only stores the content of the file laking information of the filename, so tree is used to store metadata of files (name, type, mode) and also used to group files together.

git ls-tree 67ac38590b37477deff534cc43c90d2e97a5d95a
# The result
# 100644 blob a5c19667710254f835085b99726e523457150e03    hello.txt

Finally, the commit file stores the information of the author who makes changes, the tree object containing file changes, and the reference to the previous change.

git cat-file -p 91acfacade0f2304724903f60f5648a5060aa011
# The result
# tree 67ac38590b37477deff534cc43c90d2e97a5d95a
# author locnguyenvu <[email protected]> 1669268362 +0700
# committer locnguyenvu <[email protected]> 1669268362 +0700

# First commit

Take a look at the .git/HEAD; it stores the reference to the current branch.

cat .git/HEAD
# The result
ref: refs/heads/main

cat .git/heads/main
# The result 91acfacade0f2304724903f60f5648a5060aa011
# The commit id of last commit

Moving on to the subsequent updates

Add new line to the hello.txt and create new file greeting/message.txt

echo 'This is the second line' >> hello.txt

mkdir greeting
echo 'Welcome to the firm!' >| greeting/message.txt

# Save the update
git add hello.txt greeting/message.txt
git commit -m "Second commit"

Now, in the .git/objects it should has two commit files, and the latest commit has a reference to the previous one, follow these reference you are able to trace back the git history

find .git/objects -type f | awk -F/ '{print $3$4}' | while read hash; do echo $hash - $(git cat-file -t $hash); done | grep commit
# The result
# 4cee4d23d16db606e06aa6d48776c71c3f7a5cf7 - commit
# 91acfacade0f2304724903f60f5648a5060aa011 - commit

git cat-file -p 4cee4d23d16db606e06aa6d48776c71c3f7a5cf7
# tree 89cbb66331db7f60904fc16357bc9a5536fdc4fc
# parent 91acfacade0f2304724903f60f5648a5060aa011
# author locnguyenvu <[email protected]> 1669283183 +0700
# committer locnguyenvu <[email protected]> 1669283183 +0700

# Second commit

The tree object of the latest commit includes the new version of hello.txt and new added greeting/message.txt

git ls-tree 89cbb66331db7f60904fc16357bc9a5536fdc4fc
# The result
# 040000 tree d29769fe469cb436d18793671c31520537dd2284    greeting
# 100644 blob 54af4235ea528e47478ce8941935a1b0f829b85b    hello.txt

git ls-tree d29769fe469cb436d18793671c31520537dd2284
# The result
# 100644 blob 15a16e750395af14db6260a7eb7e81e2d59fc07e    message.txt

To view the change on file hello.txt, by comparing the content of 2 blob files

diff -y <(git cat-file blob a5c19667710254f835085b99726e523457150e03) <(git cat-file blob 54af4235ea528e47478ce8941935a1b0f829b85b)

# The result show the line "This is the second line" was added on later commit

# Hello, world                         |    Hello, world
#                                      |  > This is the second line

The content of file .git/refs/head/main now should update to the latest commit id 4cee4d23d16db606e06aa6d48776c71c3f7a5cf7. Branching is a way to name the commit id; it updates eventually due to the latest commit.