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 文件中记录了三个对象:.gitignore
,file
,和 md
。其中,.gitignore
和file
都是普通的文件,所以标注为 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 值。
一个例子
假如我们的项目有 .gitignore
和 file1
这样两个文件和保存 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