写点什么

【摘】Git- 从零单排 02 期

用户头像
卡尔
关注
发布于: 2020 年 05 月 30 日
【摘】Git-从零单排 02期

前言


书接上文,我们对 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 文件。

touch 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 之后,仓库发生的变化。


  1. 针对有修改的文件 hello-git.txt 的内容,生成一个 blob 对象,这个对象能完美还原 hello-git.txt 文件当下的内容。

  2. 针对 blob 对象,外加一个头部信息(header)一起做 SHA-1 校验运算,得的校验和(一个长度为 40 的字符串)。并使用校验和的前两位做文件夹名,在这个文件夹里,存入使用后 38 位做文件名的 blob 对象。

  3. 生成 .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 之后,多了以下文件

  • COMMIT_EDITMSG

  • logs/HEAD

  • logs/refs/heads/master

  • refs/heads/master

  • objects/9e/b3d5fe6014cf11a120a26b3f8b3af2f20964bd

  • objects/62/01f623b63c5736c92895712c6038835be6a7f2


通过对比,笔者理解的 git commit 命令执行之后,Git 做了如下事情:


  1. 生成 .git/COMMIT_EDITMSG 文件,把  -m  参数之后的 commitMessage 写入到这个文件。

  2. 根据项目目录,计算得到一个 treeObject,并把这个对象写入 .git/objects/ 里,对应的就是多出了 objects/62/01f623 。

  3. 根据暂存区的内容、git 配置内容、执行时间、commitMessage、treeObject 等,生成一个 commitObject。并把这个对象写入到 .git/objects/ 里。对应的就是多出了 objects/9e/b3d5fe 。

  4. 修改 .git/HEAD 文件里的分支指针所指向的 commitObject,也就是把 9eb3d5fe6014cf11a120a26b3f8b3af2f20964bd 写入到 .git/refs/heads/master 文件里。如果没有 .git/refs/heads/master 文件,就生成一个,所以这里会新增 .git/_refs/heads/masterr 文件。

  5. 把当前的这个操作写入到对应的指针的 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。


  • tree 属性是目录结构,里面可能会有 tree 和 blob

  • parent 是父提交对象,这个属性可能有,或没有。其实还有可能会有第二父提交对象。

  • blob 的内容记录的文件内容


如果你不想使用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 基本使用流程 祝大家六一快乐


  1. 在项目目录下使用 git ini 初始化。

  2. 在项目内编辑文件,然后使用 git add 把修改的内容添加到暂存区。

  3. 然后使用 git commit 把暂存区的内容生成快照提交。

  4. 可以使用 git status 查看当前的状态。

  5. 可以使用 git log 查看历史记录。


世界上只有两种人,一种懂二进制,一种不懂二进制


发布于: 2020 年 05 月 30 日阅读数: 64
用户头像

卡尔

关注

还未添加个人签名 2019.03.01 加入

还未添加个人简介

评论

发布
暂无评论
【摘】Git-从零单排 02期