前言
    书接上文,我们对 Git 有了一个基本的认知,并且基础工作也做好了。接下来,笔者就用一个实例,对照执行命令前后文件的变化,探索以下命令的运行过程。如果,看官对命令底层原理不感兴趣,只是想知道命令怎么用,就直接看翻到文章最后。
- git init
 
- git add
 
- git status
 
 
- git commit
 
 
- git log
 
 
    行文时,系统环境为 macOS Mojave v10.14.5,Git 版本为 2.20.1 ,开发工具为 vscode 1.45.1 。
初始化 git init 
    首先,我们需要有一个工作目录(Working Directory)。工作目录可以是刚新建无文件的文件夹,也可以是一个已经有文件的文件夹。
 mkdir git-kael-projectcd git-kael-project
   复制代码
     然后,我需要使用 git init 对工作目录进行初始化。 git init 命令之后可以带上文件夹名称,如果文件夹不存在,则在当前路径下新建文件夹,并将其初始化。如果存在就直接对文件夹初始化。
 git initgit init folderName
   复制代码
     接下来,让我们看一下执行 git init 前后,工作目录的区别。
 # 未初始化ls -altotal 0drwxr-xr-x   2 apple  staff    64  5 29 22:31 .drwxr-xr-x+ 45 apple  staff  1440  5 29 22:31 ..# 初始化git initls -altotal 0drwxr-xr-x   3 apple  staff    96  5 29 22:32 .drwxr-xr-x+ 45 apple  staff  1440  5 29 22:31 ..drwxr-xr-x   9 apple  staff   288  5 29 22:32 .git # 多了一个 .git 文件夹,这就是我们说的仓库!
   复制代码
     对比,我们可以知道, git init 命令就是在工作目录的根目录下,生成一个 .git  文件夹,这个文件就是我们这个项目的 Git 仓库。它包含了几乎所有 Git 存储和操作的东西。 要想备份或复制一个版本库,只需把这个目录拷贝至另一处即可。
    我们查看一下, .git 文件夹里到底有什么
 ls -al .git/total 24drwxr-xr-x   9 apple  staff  288  5 29 21:41 .drwxr-xr-x   3 apple  staff   96  5 29 21:41 ..-rw-r--r--   1 apple  staff   23  5 29 21:41 HEAD  # HEAD指针,后文再讲-rw-r--r--   1 apple  staff  137  5 29 21:41 config  # 项目配置,使用 git config --loacl 时,配置的内容就存放在这里-rw-r--r--   1 apple  staff   73  5 29 21:41 description  # 项目描述文件,给GitWeb使用,我们不用关心drwxr-xr-x  13 apple  staff  416  5 29 21:41 hooks  # 钩子,存放的是一些 shell 脚本drwxr-xr-x   3 apple  staff   96  5 29 21:41 info  # 目录下面的 exclude 文件,是用来存放不想放置在 .gitignore 文件里的忽略模式drwxr-xr-x   4 apple  staff  128  5 29 21:41 objects  # 项目所有的 git 对象都被存放在这里,tree 对象、parent 对象、blob 对象drwxr-xr-x   4 apple  staff  128  5 29 21:41 refs  # 存放指针的位置,branch 、remote 、tag # -rw-r--r--   1 apple  staff   xx  x xx xx:xx index  # 暂存区,还未生成
   复制代码
     大家可以先对上面的目录结构稍微记忆一下。强调一下,因为还没有执行任何操作,所以现在暂存区还没被创建,也就是 index 文件!
添加到暂存区 git add
    接下来,我们新建一个 hello-git.txt 文件。
    再加入到暂存区之前,我们先查看一下
 # 当前状态下,是没有index文件的,也就是无暂存区ls -al .git/total 24drwxr-xr-x  11 apple  staff   352  5 29 22:47 .drwxr-xr-x   5 apple  staff   160  5 29 22:47 ..-rw-r--r--@  1 apple  staff    23  5 29 21:41 HEAD-rw-r--r--   1 apple  staff   137  5 29 21:41 config-rw-r--r--   1 apple  staff    73  5 29 21:41 descriptiondrwxr-xr-x  13 apple  staff   416  5 29 21:41 hooksdrwxr-xr-x   3 apple  staff    96  5 29 21:41 infodrwxr-xr-x   5 apple  staff   160  5 29 22:37 objectsdrwxr-xr-x   4 apple  staff   128  5 29 21:41 refs# 同时.git/objects下,也是没有git对象的ls -al .git/objects/total 0drwxr-xr-x   5 apple  staff  160  5 29 22:37 .drwxr-xr-x  11 apple  staff  352  5 29 22:47 ..drwxr-xr-x   2 apple  staff   64  5 29 21:41 infodrwxr-xr-x   2 apple  staff   64  5 29 21:41 pack
   复制代码
    
    然后,我们用 git add  命令,把 hello-git.txt  加入到暂存区。之后我们再查看一下
当然,我这里查看的就是执行命令后,会有变化的位置。其它位置是没有变化的,各位看官可自行验证。
 # git add 命令,后面可以带文件名(这里可以是一个或多个),也可以带符号点 . ,符号点与 --all 全等。# git add .  与 git add --all 效果一样git add hello-git.txt# 查看 .git/ ls -al .git/total 48drwxr-xr-x  11 apple  staff   352  5 29 22:47 .drwxr-xr-x   5 apple  staff   160  5 29 22:47 ..-rw-r--r--@  1 apple  staff  6148  5 29 22:47 .DS_Store-rw-r--r--@  1 apple  staff    23  5 29 21:41 HEAD-rw-r--r--   1 apple  staff   137  5 29 21:41 config-rw-r--r--   1 apple  staff    73  5 29 21:41 descriptiondrwxr-xr-x  13 apple  staff   416  5 29 21:41 hooks-rw-r--r--@  1 apple  staff    32  5 29 22:46 index # 这个暂存区终于出来!drwxr-xr-x   3 apple  staff    96  5 29 21:41 infodrwxr-xr-x   5 apple  staff   160  5 29 22:37 objectsdrwxr-xr-x   4 apple  staff   128  5 29 21:41 refs# 查看 .git/objects/ls -al .git/objects/total 0drwxr-xr-x   5 apple  staff  160  5 29 22:37 .drwxr-xr-x  11 apple  staff  352  5 29 22:47 ..drwxr-xr-x   3 apple  staff   96  5 29 22:37 e6drwxr-xr-x   2 apple  staff   64  5 29 21:41 infodrwxr-xr-x   2 apple  staff   64  5 29 21:41 pack# 继续查看 .git/objects/e6/ls -al .git/objects/e6/total 8drwxr-xr-x  3 apple  staff   96  5 29 22:37 .drwxr-xr-x  5 apple  staff  160  5 29 22:37 ..-r--r--r--  1 apple  staff   15  5 29 22:37 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
   复制代码
     到这里,我们可以清楚的看到,当我们执行了 git add hello-git.txt 之后,仓库发生的变化。
- 针对有修改的文件 hello-git.txt 的内容,生成一个 blob 对象,这个对象能完美还原 hello-git.txt 文件当下的内容。 
- 针对 blob 对象,外加一个头部信息(header)一起做 SHA-1 校验运算,得的校验和(一个长度为 40 的字符串)。并使用校验和的前两位做文件夹名,在这个文件夹里,存入使用后 38 位做文件名的 blob 对象。 
- 生成 .git/index 文件,.git/index 文件的文本编码是 ISO88591 ,并且把 blob 对象的校验和、文件名写入到 .git/index 里。我们可以通过 - git ls-files --stage查看暂存区的内容
 
    对于 git add 这个命令,其实它是 git hash-object 和 git update-index 这两个底层命令组合实现的。也就是说,如果你愿意,你是可以使用这两个底层命令来 ~~装逼~~ 操作的。 git add 只是为了简化操作而实现的上层命令。这两个底层命令后续有机会再讲!
查看状态 git status
    好了,在看官平时工作当中,可能会出现,离开工位一段时间,回来时就不记得之前在工作目录做了什么(嗯,应该只有笔者才会这样 zz)。这个时候,你可以努力回想使用 git status 查看工作目录的状态。
 git statusOn branch master
No commits yet
Changes to be committed:  (use "git rm --cached <file>..." to unstage)
	new file:   hello-git.txt# 打印的信息告诉我们,在 master 分支上,有一个还没有提交的新文件: hello-git.txt。# 括号里还告诉我们,可以使用 git rm --cached filename 来把文件从暂存区移走。
   复制代码
 
    如果你想,你也可以使用 git ls-files --stage 来 装逼 操作。
 git ls-files --stage100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0	hello-git.txt
   复制代码
 生成快照 git commit`
    把修改加入到暂存区之后,使用 git commit 命令,生成整个项目的‘全貌快照’,也就是 commitObject 。项目版本历史就是由一个个 commitObject 组成的,Git 可以将项目还原到任意一个 commitObject 。生成一个 commitObject 又叫 完成一次提交。
 # 在使用 git add 时,我们可以有选择的把文件加放到暂存区# 但是在使用 git commit 时,一定是把暂存区里包含的 blob 对象全部一起,生成一个 commitObject。# git commit --amend 后面会在应用场景里讲,可以先理解为修改前一个 commitObject。# 虽然表面上来看是这样。但是其实是不对的。commitObject 在大部分情况下,是无法修改的。git commit -m 'create hello-git.txt'[master (root-commit) 9eb3d5f] create hello-git.txt  1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 hello-git.txt
# 提示信息里,有 分支、commitObject 的校验和、commitMessage、修改的内容统计。
   复制代码
 
    老规则,我们来对比看一下, 输入 git commit 命令前后, .git 里的文件变化。
 # 下面信息是在 git commit 命令之前查看的。ls -al .git/total 32drwxr-xr-x  10 apple  staff  320  5 30 17:27 .drwxr-xr-x   5 apple  staff  160  5 30 17:27 ..-rw-r--r--   1 apple  staff   23  5 30 17:26 HEAD-rw-r--r--   1 apple  staff  137  5 30 17:26 config-rw-r--r--   1 apple  staff   73  5 30 17:26 descriptiondrwxr-xr-x  13 apple  staff  416  5 30 17:26 hooks-rw-r--r--   1 apple  staff  112  5 30 17:26 indexdrwxr-xr-x   3 apple  staff   96  5 30 17:26 infodrwxr-xr-x   5 apple  staff  160  5 30 17:26 objectsdrwxr-xr-x   4 apple  staff  128  5 30 17:26 refs# 下面信息是在 git commit 命令之后查看的。ls -al .git/total 56drwxr-xr-x  13 apple  staff   416  5 30 17:22 .drwxr-xr-x   4 apple  staff   128  5 30 17:22 ..-rw-r--r--   1 apple  staff    21  5 30 17:10 COMMIT_EDITMSG-rw-r--r--@  1 apple  staff    23  5 29 21:41 HEAD-rw-r--r--   1 apple  staff   137  5 29 21:41 config-rw-r--r--   1 apple  staff    73  5 29 21:41 descriptiondrwxr-xr-x  13 apple  staff   416  5 29 21:41 hooks-rw-r--r--   1 apple  staff   145  5 30 17:10 indexdrwxr-xr-x   3 apple  staff    96  5 29 21:41 infodrwxr-xr-x   5 apple  staff   160  5 30 17:22 logsdrwxr-xr-x   8 apple  staff   256  5 30 17:10 objectsdrwxr-xr-x   4 apple  staff   128  5 29 21:41 refs# 查看logsls -al .git/logs/total 24drwxr-xr-x   5 apple  staff   160  5 30 17:22 .drwxr-xr-x  13 apple  staff   416  5 30 17:22 ..-rw-r--r--@  1 apple  staff   165  5 30 17:10 HEADdrwxr-xr-x   4 apple  staff   128  5 30 17:22 refsls -al .git/logs/refs/heads/total 8drwxr-xr-x  3 apple  staff   96  5 30 17:10 .drwxr-xr-x  4 apple  staff  128  5 30 17:22 ..-rw-r--r--@ 1 apple  staff  165  5 30 17:10 master# 查看objectsls -al .git/objects/total 16drwxr-xr-x   8 apple  staff   256  5 30 17:10 .drwxr-xr-x  13 apple  staff   416  5 30 17:22 ..drwxr-xr-x   3 apple  staff    96  5 30 17:10 62drwxr-xr-x   3 apple  staff    96  5 30 17:10 9edrwxr-xr-x   3 apple  staff    96  5 29 22:37 e6drwxr-xr-x   2 apple  staff    64  5 29 21:41 infodrwxr-xr-x   2 apple  staff    64  5 29 21:41 packls -al .git/objects/9e/total 8drwxr-xr-x  3 apple  staff   96  5 30 17:10 .drwxr-xr-x  8 apple  staff  256  5 30 17:10 ..-r--r--r--  1 apple  staff  131  5 30 17:10 b3d5fe6014cf11a120a26b3f8b3af2f20964bdls -al .git/objects/62/total 8drwxr-xr-x  3 apple  staff   96  5 30 17:10 .drwxr-xr-x  8 apple  staff  256  5 30 17:10 ..-r--r--r--  1 apple  staff   58  5 30 17:10 01f623b63c5736c92895712c6038835be6a7f2
   复制代码
     
     对比可以发现, git commit 之后,多了以下文件
    通过对比,笔者理解的 git commit 命令执行之后,Git 做了如下事情:
- 生成 .git/COMMIT_EDITMSG 文件,把  - -m 参数之后的 commitMessage 写入到这个文件。
 
- 根据项目目录,计算得到一个 treeObject,并把这个对象写入 .git/objects/ 里,对应的就是多出了 objects/62/01f623 。 
- 根据暂存区的内容、git 配置内容、执行时间、commitMessage、treeObject 等,生成一个 commitObject。并把这个对象写入到 .git/objects/ 里。对应的就是多出了 objects/9e/b3d5fe 。 
- 修改 .git/HEAD 文件里的分支指针所指向的 commitObject,也就是把 9eb3d5fe6014cf11a120a26b3f8b3af2f20964bd 写入到 .git/refs/heads/master 文件里。如果没有 .git/refs/heads/master 文件,就生成一个,所以这里会新增 .git/_refs/heads/masterr 文件。 
- 把当前的这个操作写入到对应的指针的 logs 文件里,这里相关的指针只有两个,一个是 HEAD,另一个是 master。如果指针的 logs 文件不存在,就新建对应的文件。所以就新增了,.git/logs/HEAD 和 .git/logs/refs/heads/master。这个文件里的内容就是本地 reflog 记录,并不是后文讲的 - git log命令得到的内容。
 
    这里,笔者再详细的描述一下 commitObject。为了更好的演示,我们在当前的提交基础上,继续作一些操作。
 mkdir libtouch lib/lib.jsecho 'Git 真好用!' > lib/git.txtgit add .git commit -m 'create lib/git.txt'[master 75f75b1] create lib/git.txt 2 files changed, 1 insertion(+) create mode 100644 lib/git.txt create mode 100644 lib/lib.js
   复制代码
     我们新建了一个 lib/ 文件夹,然后在这个文件夹里,新建了一个 lib.js 文件,和一个 git.txt 文件,并在 git.txt 文件里写入了 'Git 真好用!'。接着就是 git add . 把刚的这些修改添加到暂存区,最后使用 git commit -m 'create lib/git.txt' 生成一个提交。
    我们现在得到了一个 75f75b1 的 commitObject。我们来查看一下这个对象的信息。
 # 查看 75f75b1 的类型和详情git cat-file -t 75f75b1commit # 这是一个 commit 类型的对象git cat-file -p 75f75b1tree d1d138a1890c432ef996b8713b69453a8baa339e # 75f75b1 对象的 tree 对象,对应的项目目录parent 9eb3d5fe6014cf11a120a26b3f8b3af2f20964bd # 75f75b1 对象的 parent 对象,对应着父提交对象author kael <kael_yp@foxmail.com> 1590841110 +0800 # 75f75b1 对象的作者committer kael <kael_yp@foxmail.com> 1590841110 +0800 # 75f75b1 对象的提交者
create lib/git.txt # 75f75b1 对象的提交说明 commitMessage
   复制代码
      我们继续查看 d1d138a 这个 tree 对象。
 # 查看 d1d138a 的类型和详情git cat-file -t d1d138atree # 这是一个 tree 类型的对象git cat-file -p 1d138a100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391	hello-git.txt # blob 类型的对象,hello-git.txt文件040000 tree 8bc5165a0972ca341226552000d29888be651bc9	lib # tree 类型的对象,lib 文件夹# 查看 hello-git.txt 文件git cat-file -p e69de2# 为空,因为这个文件就是一个空文件。我忘记加内容了。。。哈哈# 查看 lib 文件夹git cat-file -p 8bc516100644 blob cdb8667b7f629f26cae0ea24993bfd2c6411e33c	git.txt # blob 类型的对象,git.txt 文件100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391	lib.js # blob 类型的对象,lib.js 文件# 查看 git.txt 文件git cat-file -p cdb8667Git 真好用! # 这就是我们之前写入的内容。这就是 Git 哲学里的,通过文件快照记录版本,而不是使用 diff 。
   复制代码
     我们继续查看 9eb3d5f 这个 parent 对象。
 # 查看 9eb3d5f 的类型和详情git cat-file -t 9eb3d5fcommit # 这是一个 commit 类型的对象# 这是一个 parent 类型的对象git cat-file -p 9eb3d5ftree 6201f623b63c5736c92895712c6038835be6a7f2 # 9eb3d5f 对象的 tree 对象,对应的项目目录author kael <kael_yp@foxmail.com> 1590829805 +0800 # 9eb3d5f 对象的作者committer kael <kael_yp@foxmail.com> 1590829805 +0800 # 9eb3d5f 对象的提交者
create hello-git.txt # 9eb3d5f 对象的提交说明 commitMessage
   复制代码
     再看看 6201f62 这个 tree 对象。
 git cat-file -p 6201f62100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391	hello-git.txtgit cat-file -p e69de29# hello-git.txt 内容为空
   复制代码
     我们简单描述上面查看到的信息。 75f75b1 这个对象里有 tree 和 parent。tree 里有 blob 和 tree。然后就是 parent 里有 tree,没有 parent。
    如果你不想使用git commit  这个命令,那就使用 git write-tree 和 git commit-tree 来 ~~装逼~~ 实现。 git write-tree 命令用来将当前的目录结构,生成一个 treeObject。 git commit-tree 命令用于将目录树对象写入版本历史。有机会,后续再讲这两个命令。
查看历史 git log
git log 这个命令是最常用命令之一,可以查看指定的 指针 的提交历史。
 git log# 第一行是 类型 校验和 指针集,指针集里可能会有 HEAD、branch、tag,使用逗号分开commit 9eb3d5fe6014cf11a120a26b3f8b3af2f20964bd (HEAD -> master)# 第二行是 作者信息Author: kael <kael_yp@foxmail.com># 第三行是 时间Date:   Sat May 30 17:10:05 2020 +0800# 往下就是 commitMessage
    create hello-git.txt
   复制代码
     使用 git log 命令是不会对 .git 仓库操作的,也就是你不会有 什么时候什么方式使用了 git log 命令 的记录,因为记录这个操作,没有任何意义。
    git log 是一个超级厉害的命令。看一个人 Git 用得好不好,看 Ta 使用 git log 的姿势怎么样就能看出来。下面我就列举一些常用的 git log 使用场景和其它参数。以下选项可以组合使用
 # 正常查看历史记录git log# 查看简洁的历史记录git log --oneline# 查看带每一次提交时修改的历史记录git log -p # -p 是 --patch 的简写# 查看一定数量的历史记录git log -4 # 这里就是显示倒数的4条# 查看有简略统计信息的历史记录git log --stat# 控制历史记录的显示格式。git log --pretty=format:"%h - %an, %s" # %h 简短校验和;%an 作者;%s 提交说明;git log --pretty=online # 差不多等于 git log --oneline# 查看带结合路径的历史记录,大概就是会给 commitObject 间边上线条git log --graph# 查看指定时限内的历史记录git log --since=1.weeks # 一周内的历史记录git log --since=2020-05-01 # 查看2020年05月01号之后的提交,--since 可以写作 --after# 查看指定时间之前的提交git log --until=2020-05-01 # 查看2020年05月01号之前的提交,--until 可以写作 --before# 查看包含了指定字符的message所在的历史记录git log --grep=create # 查看包含了 ‘create’ 的提交说明的历史记录# 这个就牛逼了,查看包含了指定字符修改的历史记录git log -Svar # 查看修改里包含了 ‘var’ 的历史记录# 查看指定作者的历史记录git log --author=kael # 查看 kael 写的历史记录# 查看指定提交者的历史记录git log --committer=kael # 查看 kael 提交的历史记录
   复制代码
 
    这个提交历史记录实际是通过 commitObject 的 parent 属性连起来的,直到找到一个没有 parent 属性的 commitObject。可以跟链表对应理解。
附
    补充一下,.git/HEAD 文件的相关知识点。这个文件在 git init 时,里面的内容是 ref: refs/heads/master ,是不是很眼熟?没错,这是一个路径【 .git/refs/heads/master 】。当我们提交时,HEAD 会带着移动的分支指针。如果是第一次提交,Git 会用这个文件的内容生成对应的 分支。这也就是为什么,我们的仓库一般都会有一个 master 分支。如果你在 git init 之后(当然,你其实可以在任意时候对这个文件进行修改),把这个文件的内容改成 refs/heads/test ,那在提交时,默认就会生成 test 分支。笔者强烈建议不要改!会被同事打的!后续会讲的 git checkout 签出命令,改的就是这个文件的内容。
    补充一下,.git/logs/里文件的相关知识点。这个文件夹下的文件,它的内容是长这样的:
 cat .git/logs/HEAD # 首先是40个0,本来是这个记录的 parent 的校验和,但是因为这条记录是第一个,没有父对象,所以就用0占位# 然后是正常的40位的校验和,这是这个记录自身的校验和# 然后再是提交者,邮箱,时间# 最后就是操作类型和 message 0000000000000000000000000000000000000000 9eb3d5fe6014cf11a120a26b3f8b3af2f20964bd kael <kael_yp@foxmail.com> 1590829805 +0800	commit (initial): create hello-git.txt
   复制代码
 当我们使用 git reflog 命令时,就是把对应的指针的 logs 文件的内容稍微处理一下显示出来。
后语
最后,笔者就 大概的说一下 Git 基本使用流程 祝大家六一快乐。
- 在项目目录下使用 - git ini初始化。
 
- 在项目内编辑文件,然后使用 - git add把修改的内容添加到暂存区。
 
- 然后使用 - git commit把暂存区的内容生成快照提交。
 
- 可以使用 - git status查看当前的状态。
 
- 可以使用 - git log查看历史记录。
 
世界上只有两种人,一种懂二进制,一种不懂二进制。
评论