Thursday, June 22, 2023

git commit


A git commit records a snapshot of the tracked files in the project. It consists of a git commit object, git tree objects and git blob objects. 


Currently, I am curious to find out what Git will do for us behind the scene if we run the 'git commit' command. Below are the logs coming from my experiment. 


Reference: Git Book


Preparation


Create a brand new folder and run the 'git init' command.


Step 1: 


Create a file and run the 'git add' command.

  
    $ echo 'test' > test.txt
$ git add .


Check the .git tree structure.


$ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index (a new file)
├── info │   └── exclude ├── objects
│   ├── 9d (a new directory)
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4 (a new file for git blob object)
│   ├── info │   └── pack └── refs ├── heads └── tags 10 directories, 19 files


Run the 'git commit' command.

  
    $ git commit -m 'first commit'
[master (root-commit) 002c253] first commit 1 file changed, 1 insertion(+) create mode 100644 test.txt


The output '[master (root-commit) 002c253] first commit' means:

1. master(root-commit) => this is the first (root) commit in the master branch

2. There is a new git commit object '002c253'


Check the .git tree structure.


  $ tree .git/
.git/
├── COMMIT_EDITMSG (a new file)
├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index 
├── info │   └── exclude
    ├── logs  (a new directory)
    │   ├── HEAD (a new file)
    │   └── refs  (a new directory)
    │       └── heads  (a new directory)
    │           └── master (a new file)
├── objects
    │   ├── 00  (a new directory)
    │   │   └── 2c253e8b132d574074181a5b578ba3ea0145c6 (a new file for git commit object)
    │   ├── 2b  (a new directory)
    │   │   └── 297e643c551e76cfa1f93810c50811382f9117 (a new file for git tree object)
│   ├── 9d
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│   ├── info │   └── pack └── refs ├── heads
    │   └── master (a new file)
└── tags 15 directories, 25 files


Check the type of the git commit object '002c253'


    $ git cat-file -t 002c253
    commit


Check the content of the git commit object '002c253'

From the log, it provides the committing message, and a git tree object working with git blob objects to record the snapshot of the tracked files.


    $ git cat-file -p 002c253
    tree 2b297e643c551e76cfa1f93810c50811382f9117
    author Frank <frank@demo.com>
    committer Frank <frank@demo.com>

    first commit        


Check the type of the git tree object '2b297e'. 


    $ git cat-file -t 2b297e
    tree


Check the content of the git tree object '2b297e'. 


    $ git cat-file -p 2b297e
    100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt


According to the logs above, there are two files created after running the 'git commit' command.

1. commit object

It records the commit message, the author, the committer, the parent commit, and a git tree object.

2. tree object

It is the snapshot of the current file system.


Check the index file. (Check 'The Three Trees' chapter for more info)


    git ls-files -s
100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 test.txt


If it is the first commit, the master file will be created under .git/refs/heads/

Check the master branch info.


    cat .git/refs/heads/master
    002c253e8b132d574074181a5b578ba3ea0145c6


Step 2: 


Add a new file and run the 'git add' command


    $ echo 'test2' > test2.txt
    $ git add .


Check the .git tree structure.


  $ tree .git/
.git/
├── COMMIT_EDITMSG 
├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index 
├── info │   └── exclude
    ├── logs 
    │   ├── HEAD 
    │   └── refs 
    │       └── heads 
    │           └── master 
├── objects
    │   ├── 00 
    │   │   └── 2c253e8b132d574074181a5b578ba3ea0145c6 
    │   ├── 18 (a new directory)
    │   │   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827  (a new file for git blob object)
    │   ├── 2b 
    │   │   └── 297e643c551e76cfa1f93810c50811382f9117 
│   ├── 9d
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│   ├── info │   └── pack └── refs ├── heads
    │   └── master 
└── tags 16 directories, 26 files


Run the 'git commit' command.


    $ git commit -m 'second commit'
[master aea662f] second commit 1 file changed, 1 insertion(+) create mode 100644 test2.txt
   


Check the .git tree structure.


  $ tree .git/
.git/
├── COMMIT_EDITMSG
├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index 
├── info │   └── exclude
    ├── logs 
    │   ├── HEAD 
    │   └── refs 
    │       └── heads 
    │           └── master 
├── objects
    │   ├── 00 
    │   │   └── 2c253e8b132d574074181a5b578ba3ea0145c6 
    │   ├── 18 
    │   │   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827  
    │   ├── 59 (a new directory)
    │   │   └── 50f5c8ec9efcb96612f89264fd72994badb828 (a new file for git tree object)
    │   ├── 2b 
    │   │   └── 297e643c551e76cfa1f93810c50811382f9117 
│   ├── 9d
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│   ├── ae (a new directory) │   │   └── a662fe0a0621247fadb98f25a35b774343335a (a new file for git commit object)
│   ├── info │   └── pack └── refs ├── heads
    │   └── master 
└── tags 18 directories, 28 files


Check the content of the git commit object 'aea662f'.


    $ git cat-file -p aea662f
tree 5950f5c8ec9efcb96612f89264fd72994badb828 parent 002c253e8b132d574074181a5b578ba3ea0145c6
    author Frank <frank@demo.com>
    committer Frank <frank@demo.com>

    second commit


Because it is not the first commit, the parent pointer will point to the first commit '002c253'.


Check the content of the git tree object '5950f5'.


    $ git cat-file -p 5950f5
100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt 100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test2.txt


Check the index file. (Check 'The Three Trees' chapter for more info)


    git ls-files -s
100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 test.txt 100644 180cf8328022becee9aaa2577a8f84ea2b9f3827 0 test2.txt


Step 3:


Create a new file 'test3.txt' under a new folder 'subfolder'

Run the 'git add' command.


$ mkdir subfolder
$ cd subfolder
$ echo 'test3' > test3.txt
$ cd ..
    $ git add .


Check the .git tree structure.


  $ tree .git/
.git/
├── COMMIT_EDITMSG
├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index 
├── info │   └── exclude
    ├── logs 
    │   ├── HEAD 
    │   └── refs 
    │       └── heads 
    │           └── master 
├── objects
    │   ├── 00 
    │   │   └── 2c253e8b132d574074181a5b578ba3ea0145c6 
    │   ├── 18 
    │   │   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827  
    │   ├── 2b 
    │   │   └── 297e643c551e76cfa1f93810c50811382f9117 
    │   ├── 59 
    │   │   └── 50f5c8ec9efcb96612f89264fd72994badb828 
│   ├── 9d
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│   ├── ae │   │   └── a662fe0a0621247fadb98f25a35b774343335a
    │   ├── df (a new directory)
    │   │   └── 6b0d2bcc76e6ec0fca20c227104a4f28bac41b (a new file for git blob object)
│   ├── info
│   └── pack └── refs ├── heads
    │   └── master 
└── tags 19 directories, 29 files

A blob file was created to track the new added file content, but without folder info.


Run the 'git commit' command.


    $ git commit -m 'third commit'
[master d2633af] third commit
    1 file changed, 1 insertion(+)
    create mode 100644 subfolder/test3.txt


Check the .git tree structure.


  $ tree .git/
.git/
├── COMMIT_EDITMSG
├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample
├── index 
├── info │   └── exclude
    ├── logs 
    │   ├── HEAD 
    │   └── refs 
    │       └── heads 
    │           └── master 
├── objects
    │   ├── 00 
    │   │   └── 2c253e8b132d574074181a5b578ba3ea0145c6 
    │   ├── 18 
    │   │   └── 0cf8328022becee9aaa2577a8f84ea2b9f3827  
    │   ├── 2b 
    │   │   └── 297e643c551e76cfa1f93810c50811382f9117
    │   ├── 59 
    │   │   └── 50f5c8ec9efcb96612f89264fd72994badb828 
    │   ├── 96 (a new directory)
    │   │   └── eb8e82f67bc50c83ea59bb1e993d559754784b (a new file for git tree object)
│   ├── 9d
│   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
│   ├── ae │   │   └── a662fe0a0621247fadb98f25a35b774343335a
    │   ├── d2 (a new directory)
    │   │   └── 633af8288737f9c19cf4e69b12c6083f34646e (a new file for git commit object)
    │   ├── df 
    │   │   └── 6b0d2bcc76e6ec0fca20c227104a4f28bac41b 
    │   ├── e1 (a new directory)
    │   │   └── 452d77cf5821a8b20b8fba8add0c5804073a8f (a new file for git tree object)
│   ├── info
│   └── pack └── refs ├── heads
    │   └── master 
└── tags 22 directories, 32 files


Check the content of the git commit object 'd2633af'.


    $ git cat-file -p d2633af
tree 96eb8e82f67bc50c83ea59bb1e993d559754784b parent aea662fe0a0621247fadb98f25a35b774343335a
    author Frank <frank@demo.com>
    committer Frank <frank@demo.com>

    third commit


Check the content of the git tree object '96eb8e'

$ git cat-file -p 96eb8e
040000 tree e1452d77cf5821a8b20b8fba8add0c5804073a8f subfolder 100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 test.txt 100644 blob 180cf8328022becee9aaa2577a8f84ea2b9f3827 test2.txt


Check the content of the git tree object 'e1452d'.


    $ git cat-file -p e1452d
    100644 blob df6b0d2bcc76e6ec0fca20c227104a4f28bac41b    test3.txt


According to the logs above:

1. No git blob object for folders 2. New git tree object will be created for tracking folders

Check the index file. 

It is similar with the git tree object, but it provide subfolder info.


    git ls-files -s
100644 df6b0d2bcc76e6ec0fca20c227104a4f28bac41b 0 subfolder/test3.txt 100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 test.txt 100644 180cf8328022becee9aaa2577a8f84ea2b9f3827 0 test2.txt


Wednesday, June 21, 2023

git add


Currently, I am curious to find out what Git will do for us behind the scene if we run the 'git add' command. Below are the logs coming from my experiment. 


Reference: Git Book


Preparation


Create a brand new folder and run the 'git init' command.


Step 1: 

Dump the .git tree structure first.


$ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample ├── info │   └── exclude ├── objects │   ├── info │   └── pack └── refs ├── heads └── tags 9 directories, 17 files


Step 2:

Create a new file and use the 'git add' command to add it to the 'staging area' (Changes to be committed).


    $ echo 'test' > test.txt
    $ git add .


Check the .git tree structure.


    $ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample ├── index (a new file)
├── info │   └── exclude ├── objects │   ├── 9d (a new directory) │   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4 (a new file for git blob object) │   ├── info │   └── pack └── refs ├── heads
└── tags 10 directories, 19 files

After running the 'git add' command, there are two files and one directory created.

    1 directory:

        9d

    2 files:

        aeafb9864cf43055ae93beb0afd6c7d144bfa4

        index


9daeafb9864cf43055ae93beb0afd6c7d144bfa4 is a SHA-1 hash key which is calculated by the combination of the 'blob' type, the content of the file, and content length, and it is a reference (also, the filename of git objects) to finding the mapping git blob object storing the content. (Refer to 'Object Storage' topic in Git Book)


We can use the 'git cat-file' command to show the file content via the SHA-1 key:


$ git cat-file -p 9daeaf
test


Check its git object type:


$ git cat-file -t 9daeaf
blob


Check its git object length:


$ git cat-file -s 9daeaf
5


index (Staging area) connects 'working directory' and 'git directory (Repository)', and it records the filename and SHA-1 Hash key of the file snapshot.


Using 'git ls-file -s' to show the index file.


git ls-files -s
  100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0       test.txt

Based on this file structure, we know the filename and its content through the index file and git blob objects.


Step 3:

Add a new file with the same content.


    $ echo 'test' > test2.txt
    $ git add .


Check the .git tree structure.


    $ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample ├── index
├── info │   └── exclude ├── objects │   ├── 9d │   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4 │   ├── info │   └── pack └── refs ├── heads
└── tags 10 directories, 19 files

Because those two files (test.txt and test2.txt) have the same content, Git does not need to create an extra git blob object to track the new file. (Also, the blob file does not record the filename)


Check the index file.

We notice that those two files refer to the same SHA-1 hash key because they have the same content.


git ls-files -s
100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 test.txt 100644 9daeafb9864cf43055ae93beb0afd6c7d144bfa4 0 test2.txt


Step 4:

Remove 'test2.txt' and replace the 'test.txt' file with different content.


    $ rm test2.txt
    $ echo 'hello' > test.txt
    $ git add .


Check the .git tree structure.


    $ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample ├── index ├── info │   └── exclude ├── objects │   ├── 9d │   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4 │   ├── ce (a new directory) │   │   └── 013625030ba8dba906f756967f9e9ca394464a (a new file for git blob object) │   ├── info │   └── pack └── refs ├── heads └── tags 11 directories, 20 files

Git will create a new git blob object to track the new content of 'test.txt'.

Also, Git does not delete any git objects even if you delete related files from a working directory (such as 9daeafb).


Check the index file. 

The filename 'test.txt' refers to a new SHA-1 hash key.


git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 test.txt


Step 5:

Create a new directory and create a new file under it.


    $ mkdir sub
    $ cd sub
    $ echo 'hello from subfolder' > test2.txt
    $ cd ..
    $ git add .

Check the .git tree structure.


    $ tree .git/
.git/ ├── HEAD ├── branches ├── config ├── description ├── hooks │   ├── applypatch-msg.sample │   ├── commit-msg.sample │   ├── fsmonitor-watchman.sample │   ├── post-update.sample │   ├── pre-applypatch.sample │   ├── pre-commit.sample │   ├── pre-merge-commit.sample │   ├── pre-push.sample │   ├── pre-rebase.sample │   ├── pre-receive.sample │   ├── prepare-commit-msg.sample │   ├── push-to-checkout.sample │   └── update.sample ├── index ├── info │   └── exclude ├── objects │   ├── 9d │   │   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4 │   ├── ce │   │   └── 013625030ba8dba906f756967f9e9ca394464a
│   ├── ed (a new directory) │   │   └── 91b9af9b0a3e32ef72b93a26677d112583504f (a new file for git blob object) │   └── pack └── refs ├── heads └── tags 12 directories, 21 files


Check its git object content:


$ git cat-file -p ed91b9
hello from subfolder


Check its git object type:


$ git cat-file -t ed91b9
blob

From the git blob object, we cannot know the directory information.


Check the index file. 

The directory information is stored here.


git ls-files -s
100644 ed91b9af9b0a3e32ef72b93a26677d112583504f 0 sub/test2.txt 100644 ce013625030ba8dba906f756967f9e9ca394464a 0 test.txt