【摘】Git-从零单排 02期

前言


书接上文,咱们对Git有了一个基本的认知,而且基础工做也作好了。接下来,笔者就用一个实例,对照执行命令先后文件的变化,探索如下命令的运行过程。若是,看官对命令底层原理不感兴趣,只是想知道命令怎么用,就直接看翻到文章最后。git

  • git init
  • git add
  • git status
  • git commit
  • git log


行文时,系统环境为 macOS Mojave v10.14.5,Git版本为 2.20.1 ,开发工具为 vscode 1.45.1 。
shell

初始化 git init

首先,咱们须要有一个工做目录(Working Directory)。工做目录能够是刚新建无文件的文件夹,也能够是一个已经有文件的文件夹。bash

mkdir git-kael-project
cd git-kael-project
复制代码

而后,我须要使用 git init 对工做目录进行初始化。 git init 命令以后能够带上文件夹名称,若是文件夹不存在,则在当前路径下新建文件夹,并将其初始化。若是存在就直接对文件夹初始化。app

git init
git init folderName
复制代码

接下来,让咱们看一下执行 git init 先后,工做目录的区别。工具

# 未初始化
ls -al
total 0
drwxr-xr-x   2 apple  staff    64  5 29 22:31 .
drwxr-xr-x+ 45 apple  staff  1440  5 29 22:31 ..
# 初始化
git init
ls -al
total 0
drwxr-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 24
drwxr-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 文件!
ui

添加到暂存区 git add

接下来,咱们新建一个 hello-git.txt 文件。编码

touch hello-git.txt
复制代码

再加入到暂存区以前,咱们先查看一下spa

# 当前状态下,是没有index文件的,也就是无暂存区
ls -al .git/
total 24
drwxr-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 description
drwxr-xr-x  13 apple  staff   416  5 29 21:41 hooks
drwxr-xr-x   3 apple  staff    96  5 29 21:41 info
drwxr-xr-x   5 apple  staff   160  5 29 22:37 objects
drwxr-xr-x   4 apple  staff   128  5 29 21:41 refs
# 同时.git/objects下,也是没有git对象的
ls -al .git/objects/
total 0
drwxr-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 info
drwxr-xr-x   2 apple  staff   64  5 29 21:41 pack
复制代码

而后,咱们用 git add  命令,把 hello-git.txt  加入到暂存区。以后咱们再查看一下3d

固然,我这里查看的就是执行命令后,会有变化的位置。其它位置是没有变化的,各位看官可自行验证。

# git add 命令,后面能够带文件名(这里能够是一个或多个),也能够带符号点 . ,符号点与 --all 全等。
# git add . 与 git add --all 效果同样
git add hello-git.txt
# 查看 .git/ 
ls -al .git/
total 48
drwxr-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 description
drwxr-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 info
drwxr-xr-x   5 apple  staff   160  5 29 22:37 objects
drwxr-xr-x   4 apple  staff   128  5 29 21:41 refs
# 查看 .git/objects/
ls -al .git/objects/
total 0
drwxr-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 e6
drwxr-xr-x   2 apple  staff   64  5 29 21:41 info
drwxr-xr-x   2 apple  staff   64  5 29 21:41 pack
# 继续查看 .git/objects/e6/
ls -al .git/objects/e6/
total 8
drwxr-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 status
On 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 --stage
100644 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 32
drwxr-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 description
drwxr-xr-x  13 apple  staff  416  5 30 17:26 hooks
-rw-r--r--   1 apple  staff  112  5 30 17:26 index
drwxr-xr-x   3 apple  staff   96  5 30 17:26 info
drwxr-xr-x   5 apple  staff  160  5 30 17:26 objects
drwxr-xr-x   4 apple  staff  128  5 30 17:26 refs
# 下面信息是在 git commit 命令以后查看的。
ls -al .git/
total 56
drwxr-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 description
drwxr-xr-x  13 apple  staff   416  5 29 21:41 hooks
-rw-r--r--   1 apple  staff   145  5 30 17:10 index
drwxr-xr-x   3 apple  staff    96  5 29 21:41 info
drwxr-xr-x   5 apple  staff   160  5 30 17:22 logs
drwxr-xr-x   8 apple  staff   256  5 30 17:10 objects
drwxr-xr-x   4 apple  staff   128  5 29 21:41 refs
# 查看logs
ls -al .git/logs/
total 24
drwxr-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 HEAD
drwxr-xr-x   4 apple  staff   128  5 30 17:22 refs
ls -al .git/logs/refs/heads/
total 8
drwxr-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
# 查看objects
ls -al .git/objects/
total 16
drwxr-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 62
drwxr-xr-x   3 apple  staff    96  5 30 17:10 9e
drwxr-xr-x   3 apple  staff    96  5 29 22:37 e6
drwxr-xr-x   2 apple  staff    64  5 29 21:41 info
drwxr-xr-x   2 apple  staff    64  5 29 21:41 pack
ls -al .git/objects/9e/
total 8
drwxr-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 b3d5fe6014cf11a120a26b3f8b3af2f20964bd
ls -al .git/objects/62/
total 8
drwxr-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,也就是把 9e__b3d5fe6014cf11a120a26b3f8b3af2f20964bd_ 写入到 .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 lib
touch lib/lib.js
echo 'Git 真好用!' > lib/git.txt
git 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 75f75b1
commit # 这是一个 commit 类型的对象
git cat-file -p 75f75b1
tree 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 d1d138a
tree # 这是一个 tree 类型的对象
git cat-file -p 1d138a
100644 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 8bc516
100644 blob cdb8667b7f629f26cae0ea24993bfd2c6411e33c	git.txt # blob 类型的对象,git.txt 文件
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391	lib.js # blob 类型的对象,lib.js 文件
# 查看 git.txt 文件
git cat-file -p cdb8667
Git 真好用! # 这就是咱们以前写入的内容。这就是 Git 哲学里的,经过文件快照记录版本,而不是使用 diff 。

复制代码

咱们继续查看 9eb3d5f 这个 parent 对象。

# 查看 9eb3d5f 的类型和详情
git cat-file -t 9eb3d5f
commit # 这是一个 commit 类型的对象
# 这是一个 parent 类型的对象
git cat-file -p 9eb3d5f
tree 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 6201f62
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391	hello-git.txt
git 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。能够跟链表对应理解。

未命名.001.jpeg

补充一下,.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 查看历史记录。


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

相关文章
相关标签/搜索