Git commit 提交记录

Git 的三个阶段分别是:modified,staged,commited。最后一个阶段 commited (提交)由 git commit 命令完成,commit 之后,对于文件的相应的修改将会被保存在 Git 仓库当中。Git 存储的不是一个文件相对于前一个版本的变化的部分,而是当前文件的快照。所有的记录信息被存储在 .git/objects 文件夹下面。

Git commit 命令用法简介

如果有 stage 的文件,则需要使用 git commit 来将文件记录进 Git 仓库。最简单的用法就是:

git commit

该命令会调用默认的编辑器,来编辑 commit 的注释信息。一般默认编辑器是 VIM,默认编辑器可以通过 git config --local core.editor=emacs 来设置为 emacs 或其他编辑器。

一般情况下,注释比较简短,使用编辑器编辑会比较繁琐,所以可以直接在 commit 的时候加上注释,使用 -m 参数即可。

git commit -m "commit message"

如果在 commit 的时候需要使用特定的模板,则可以使用 git commit -t <file>,这样可以在打开编辑器的时候自动在其中加入模板的信息。

如果在一次 commit 之后,发现有文件需要添加或者需要修改 commit 的注释,可以使用 git commit --amend 来修改上次 commit 的信息。

Git commit 的快照结构

Git commit 命令会存储当前的一个快照,相应的数据都存在 .git/objects 文件夹中, 以 SHA-1 命名。

.git/objects 文件夹下有若干子文件夹: * objects/[0-9a-f][0-9a-f]:存储对象的文件夹,以 SHA-1 的前两个字母命名。存储对象的子文件夹下面存储有所有 SHA-1 前两个字符与子文件夹名相同的所有 commit 文件,tree 文件和具体的 blob 文件,以原 SHA-1 除去前两个字符剩下的字符命名文件。 * objects/info:文件夹,关于存储在该文件下中的对象的其他信息 * objects/pack:文件夹,存储压缩的对象文件

一个快照由三部分组成: 1. 当前 commit 的文件,其中包括commit的信息(author和message等),对应的tree(树文件)的 SHA-1和对应的parent commit 的SHA-1(如果有) 2. 当前 commit 的 tree 文件,tree中包括了当前快照中包括的所有的 blob (块文件)的 SHA-1 3. 当前 commit 的所有的 blob 文件

为了节约空间,以上所有的文件都是以压缩存储的。

commit 文件

commit 文件中存储有该 commit 的信息。使用 git log --pretty=oneline,我们可以知道所有的 commit 的情况:

d144d896cc3a57a21f1fdd443b2b76620584fd76 (HEAD -> master) add markdown notes
bdc705169c09f886182d5dfe731f241c3a4e0586 (origin/master) add ignore
6823658394fecca1415c17bccebf8dba3ed0cc54 first file

我们可以知道当前一共有三个 commits。当前的 HEAD 的 commit 为 d144d896cc3a57a21f1fdd443b2b76620584fd76。 远程的 origin/master HEAD 为 bdc705169c09f886182d5dfe731f241c3a4e0586。 第一个 commit 为 6823658394fecca1415c17bccebf8dba3ed0cc54。 这些 SHA-1 值就是所有的 commit 文件的文件名,在 .git/objects 文件下我们可以看到:

│   ├── objects
│   │   ├── 68
│   │   │   └── 23658394fecca1415c17bccebf8dba3ed0cc54
│   │   ├── bd
│   │   │   └── c705169c09f886182d5dfe731f241c3a4e0586
│   │   ├── d1
│   │   │   ├── 44d896cc3a57a21f1fdd443b2b76620584fd76

使用 git cat-file -p d144d896cc3a57a21f1fdd443b2b76620584fd76 可以去看该 commit 文件中的具体信息:

tree 2b548176ae75a1f62d6635409cc70c1ff6c2f25b
parent bdc705169c09f886182d5dfe731f241c3a4e0586
author Wolfson Liu
committer Wolfson Liu

add markdown notes

一个 commit 文件中包括: * tree 文件的 SHA-1 值,例如:2b548176ae75a1f62d6635409cc70c1ff6c2f25b * parent commit 文件的 SHA-1 值,如果是第一个 commit 则没有,例如:bdc705169c09f886182d5dfe731f241c3a4e0586 * author 的信息和committer 的信息,例如:Wolfson Liu * commit 的注释信息,例如:add markdown notes

tree 文件

tree 文件中记录了当前 commit 所记录的所有文件快照(blob 文件)的存放位置。查询 commit 文件可以获得其对应的 tree 文件的信息。

例如上面我们查看 commit 文件 d144d896cc3a57a21f1fdd443b2b76620584fd76,可以知道其 tree 文件为 2b548176ae75a1f62d6635409cc70c1ff6c2f25b

依然可以使用 git cat-file -p 2b548176ae75a1f62d6635409cc70c1ff6c2f25b 显示 tree 文件内容:

100644 blob b25c15b81fae06e1c55946ac6270bfdb293870e8    .gitignore
100644 blob 303ff981c488b812b6215f7db7920dedb3b59d9a    file1
040000 tree 02a5f419fa69f1df550172be067237223b246d36    md

可以看出当前 tree 文件中记录了三个对象:.gitignorefile,和 md。其中,.gitignorefile都是普通的文件,所以标注为 blob。而 md 为文件夹,则标注为 tree。

我们使用 git cat-file -p 02a5f419fa69f1df550172be067237223b246d36 继续看 md 的内容:

100644 blob d14ca1be4beb6730ad33df72ceec1201692477c9    git_config.md
100644 blob 16a63a284776906e0949a93567a0a35145e27067    git_file_structure.md
100644 blob 0fb6d6e6201ad79b3d74b77493d0a7b9b439a018    git_logic.md
100644 blob 9e31cfc76e9ef587b6af3fd4b09e3d09afb6dd26    understand_git_index.md
100644 blob 50d4402de7d0bbcfae79a23cd910c2eb7e61db30    version_control_brief.md

可以得到该文件夹下面所包含的所有文件对应的信息。因为该文件夹下面全是文件,而没有文件夹,所有所有的对象都是 blob。而如果还有子文件夹,则子文件夹也会存储为 tree 文件。

blob 文件

blob 文件中就是具体的文件的快照了。 例如,我们使用 git cat-file -p 303ff981c488b812b6215f7db7920dedb3b59d9a 查看 file1 文件,得到的就是其内容:first file。对于其他的 blob 文件也是类似的。 所以一个 blob 文件中存储的内容是压缩过的具体的 commit 时候的文件,也就是该时刻的一个快照。也就是说,所有 commit 时候的文件都会存一份下来,消耗掉一定的存储空间。

为了节约存储空间,git 主要采用了两个策略: 1. 对于所有的文件,全部压缩存储 2. 对于完全相同的文件,即 SHA-1 值计算出来一样的文件,也只留存一次。

所以,对于一个文件自建立之初就没有变动过,其在 objects 中只会存放一次,之后所有 commit 的 tree 文件中都会记录这一个文件的 SHA-1 值。

一个例子

假如我们的项目有 .gitignorefile1 这样两个文件和保存 markdown 笔记的 md 文件夹, .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-push.sample
│   │   ├── pre-rebase.sample
│   │   ├── pre-receive.sample
│   │   ├── prepare-commit-msg.sample
│   │   └── update.sample
│   ├── index
│   ├── info
│   │   └── exclude
│   ├── logs
│   │   ├── HEAD
│   │   └── refs
│   │       ├── heads
│   │       │   └── master
│   │       └── remotes
│   │           └── origin
│   │               └── master
│   ├── objects
│   │   ├── 02
│   │   │   └── a5f419fa69f1df550172be067237223b246d36
│   │   ├── 0f
│   │   │   └── b6d6e6201ad79b3d74b77493d0a7b9b439a018
│   │   ├── 16
│   │   │   └── a63a284776906e0949a93567a0a35145e27067
│   │   ├── 2a
│   │   │   └── 65a9ad4587f6c689dc66b77917ff96f5afe653
│   │   ├── 2b
│   │   │   └── 548176ae75a1f62d6635409cc70c1ff6c2f25b
│   │   ├── 30
│   │   │   └── 3ff981c488b812b6215f7db7920dedb3b59d9a
│   │   ├── 50
│   │   │   └── d4402de7d0bbcfae79a23cd910c2eb7e61db30
│   │   ├── 68
│   │   │   └── 23658394fecca1415c17bccebf8dba3ed0cc54
│   │   ├── 7e
│   │   │   └── 03b5bfc52c8e4cf3cb422ef802fa36254d20a5
│   │   ├── 9e
│   │   │   └── 31cfc76e9ef587b6af3fd4b09e3d09afb6dd26
│   │   ├── b2
│   │   │   └── 5c15b81fae06e1c55946ac6270bfdb293870e8
│   │   ├── bd
│   │   │   └── c705169c09f886182d5dfe731f241c3a4e0586
│   │   ├── d1
│   │   │   ├── 44d896cc3a57a21f1fdd443b2b76620584fd76
│   │   │   └── 4ca1be4beb6730ad33df72ceec1201692477c9
│   │   ├── info
│   │   └── pack
│   └── refs
│       ├── heads
│       │   └── master
│       ├── remotes
│       │   └── origin
│       │       └── master
│       └── tags
├── .gitignore
├── file1
├── file1~
└── md
    ├── git_config.md
    ├── git_file_structure.md
    ├── git_logic.md
    ├── understand_git_index.md
    └── version_control_brief.md
By @Wolfson Liu in
Tags : #git,