目录
添加文件
查看objects中的文件
小结
修改文件
版本回退
回退的回退
小结
撤销修改
情况一:对于工作区的代码,还没有 add
情况二:已经 add ,但没有 commit
情况三:已经 add ,并且也 commit 了
小结
删除文件
#:首先引入一个未进行任何操作的 ./git 内部情况
[qcr@ecs-205826 project]$ tree .git
.git
├── branches
├── config
├── description
├── HEAD
├── hooks
│?? ├── applypatch-msg.sample
│?? ├── commit-msg.sample
│?? ├── post-update.sample
│?? ├── pre-applypatch.sample
│?? ├── pre-commit.sample
│?? ├── prepare-commit-msg.sample
│?? ├── pre-push.sample
│?? ├── pre-rebase.sample
│?? └── update.sample
├── info
│?? └── exclude
├── objects
│?? ├── info
│?? └── pack
└── refs
├── heads
└── tags
9 directories, 13 files
我们可以找到对应的:objects、HEAD,但是没有对应的index,master。
Note: git commit 后面的 -m 选项,描述本次提交的 message(由用户自己完成),这部分内容绝对不能省略,并且要好好描述,是用来记录我们的提交细节。
可以看见 Git 的提示:一个文件改变,是新增了一行的内容。
补充:
$ git log
打印出来所有时间从近到远的提交记录。
[qcr@ecs-205826 project]$ git log commit e797df72c0669a2b81339ff4854328126d2e6c8e Author: qcr <[email protected]> Date: Wed Jun 14 19:29:48 2023 +0800 一行你好,用于学习git
"e797df72c0669a2b81339ff4854328126d2e6c8e":commit ID,每一次提交都会有一个 commit ID,其是经过哈希计算出来的非常大的一个数字(用十六进制表示)。
如果觉得这个内容眼花缭乱的,可以给这个命令加一个选项参数。
[qcr@ecs-205826 project]$ git log --pretty=oneline e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git
现在再让我们看一看 ./git
[qcr@ecs-205826 project]$ tree .git
.git
├── branches
├── COMMIT_EDITMSG
├── config
├── description
├── HEAD
├── hooks
│ ├── applypatch-msg.sample
│ ├── commit-msg.sample
│ ├── post-update.sample
│ ├── pre-applypatch.sample
│ ├── pre-commit.sample
│ ├── prepare-commit-msg.sample
│ ├── pre-push.sample
│ ├── pre-rebase.sample
│ └── update.sample
├── index
├── info
│ └── exclude
├── logs
│ ├── HEAD
│ └── refs
│ └── heads
│ └── master
├── objects
│ ├── 38
│ │ └── 1feea9040943c8fa00ac3312d93ed7d97c42ff
│ ├── e6
│ │ └── 076a05b53658ebd812398523da7f38fc552aa1
│ ├── e7
│ │ └── 97df72c0669a2b81339ff4854328126d2e6c8e
│ ├── info
│ └── pack
└── refs
├── heads
│ └── master
└── tags
15 directories, 21 files
可以发现其与之前没进行任何操作的 ./git 多了很多很多的东西。而之前未出现的 index 出现了,就是由于 git add 而出现的暂存区,而 add 的内容就是添加到这个里面的。这个时候再来看 HEAD ,其就是一个指针,指向了 master 。
[qcr@ecs-205826 project]$ cat .git/HEAD
ref: refs/heads/master
通过查看我们可以发现,其确实就是指向 master 的,并且该段路径就是在 ./git 可以找到的。
然后我们打印 HEAD 内指向的 master 内容可以发现。
[qcr@ecs-205826 project]$ cat .git/refs/heads/master
e797df72c0669a2b81339ff4854328126d2e6c8e
这个 master 里面存放的其实就是最新的一次提交的 commit ID 。而之前又有所提到,master 里面放的都是一些索引,这些索引执指向的又其实是对象库中的对象。接下来再将我们的视角放在 objects 中多的东西上。
可以看到里面更新了一堆的对象,其实它们就是一些文件。
首先我们要将 commit ID 分为两个部分。
下述其实还有一项 parent ,后面跟的是commit ID 意为上一次的提交。
[qcr@ecs-205826 project]$ git cat-file -p e797df72c0669a2b81339ff4854328126d2e6c8e
tree 381feea9040943c8fa00ac3312d93ed7d97c42ff
author qcr <[email protected]> 1686742188 +0800
committer qcr <[email protected]> 1686742188 +0800
一行你好,用于学习git
-p:pertty更漂亮的打印出来。
其中的 tree 我们可以打印其后面的 commit ID 。
[qcr@ecs-205826 project]$ git cat-file -p 381feea9040943c8fa00ac3312d93ed7d97c42ff
100644 blob e6076a05b53658ebd812398523da7f38fc552aa1 test.txt
可以发现此处出现了我们前面的 test.txt 文件,并且其对应着一个 commit ID ,也就是对应一个索引,我们可以使用 cat-file 进行查看。
[qcr@ecs-205826 project]$ git cat-file -p e6076a05b53658ebd812398523da7f38fc552aa1
你好
可以发现正是我们在 test.txt 文件中新增的一行内容,这就是我们对于test.txt文件的一次修改,被 Git 记录下来了,所以我们每一次提交的修改都会被 Git 记录下来,就是通过对象记录下来的修改。对于objects一句话:修改的工作区内容会被写入到对象库的一个新的git对象中。
git仓库 中有一个 index(暂存区),用于放add后的一些新增内容的。HEAD是一个指针,指向的是 .git/refs/heads/master ,master 里面存放的是我们最新的一次提交的 commit ID,其又是一个索引,对应着的是一个 git 对象,该对象被维护在对象库中。对应的对象也有其 commit ID 进行查看,可以发现里面就有存放我们所修改的所有内容。
Note:
明确一个观念:Git 追踪管理的其实是修改,而不是文件
并且这是可以在对象库中体现出来,对象库中的一个对象,里面存储的是修改的工作区中的内容。
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
此时,在工作区中已经对 test.txt 进行了更改,而 Git 中为我们提供了一个命令用于查看当前仓库的一个状态。
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
用于查看我们上一次提交之后到现在是否对文件进行过修改,可以根据上述发现:Changes not staged for commit(没有将要被提交的暂存区内容),也就是说暂存区中目前是干净的没有内容。
修改的是在工作区中修改的,修改的文件就是 test.txt 。git status只能查哪个文件修改了,具体修改的是哪一些内容是不知道的。Git 还提供了一个命令,用于显示目前暂存区和工作区之间的差异的。
[qcr@ecs-205826 project]$ git diff test.txt
diff --git a/test.txt b/test.txt
index e6076a0..0891bd4 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
你好
+hello
[qcr@ecs-205826 project]$ git diff HEAD test.txt
diff --git a/test.txt b/test.txt
index e6076a0..0891bd4 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
你好
+hello
此时如果我们将 test.txt 文件进行上传,再进行 diff 命令操作可以发现。暂存区和工作区文件的没有了差异,于是什么都不会说明,而版本库和工作区文件的之间还是有差异的。
[qcr@ecs-205826 project]$ git add test.txt
[qcr@ecs-205826 project]$ git diff test.txt
[qcr@ecs-205826 project]$ git diff HEAD test.txt
diff --git a/test.txt b/test.txt
index e6076a0..0891bd4 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
你好
+hello
使用 status 命令操作,其提示我们需要将修改提交至本地仓库。
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: test.txt
#
提示了一个在暂存区中修改的文件:test.txt。
[qcr@ecs-205826 project]$ git commit -m "新增一行"
[master a8760f6] 新增一行
1 file changed, 1 insertion(+)
[qcr@ecs-205826 project]$ git diff test.txt
[qcr@ecs-205826 project]$ git diff HEAD test.txt
工作区、暂存区、版本库全部一致,所以没任何提示。
[qcr@ecs-205826 project]$ git status
# On branch master
nothing to commit, working directory clean
这一次我们已经提交成功,所以给我们打印的一个状态就是没有什么可以提交了,工作区是干净的。
之前我们也提到过,Git 能够管理文件的历史版本,这也是版本控制器重要的能力。如果有⼀天你发现之前前的工作做的出现了很大的问题,需要在某个特定的历史版本重新开始,这个时候,就需要版本回退的功能了。
执行 git reset 命令用于回退版本,可以指定退回某⼀次提交的版本。要解释⼀下 "回退" 本质是要将版本库中的内容进行回退,工作区或暂存区是否回退由命令参数决定:
$ git reset [--soft | --mixed | --hard] [HEAD]
选项 | 具体功能 |
--soft | 参数对于工作区和暂存区的内容都不变,只是将版本库回退到某个指定版本 |
--mixed | 为默认选项,使用时可以不用带该参数。该参数将暂存区的内容退回为指定提交版本内容,工作区文件保持不变。 |
--hard | 将暂存区与工作区都退回到指定版本。切记工作区有未提交的代码时不要用这个命令,因为⼯作区会回滚,你没有提交的代码就再也找不回了,所以使用该参数前⼀定要慎重。 |
Note:关于 --hard 选项一定需要注意(慎用!!!),如果当前正在工作区中进行开发,一旦回退是会把正在开发的数据全部干掉,并且是不管如何操作都是找不回对应的开发数据的。
现在之前的基础上再提交几个文件(用于实现效果的体现),下述 test1、test2、test3 是用一句 add 和一句 commit 提交的。
[qcr@ecs-205826 project]$ ls
test1 test2 test3 test.txt
[qcr@ecs-205826 project]$ git log --pretty=oneline
1bba34a04f231d8ad49f3ce5079879144cf6f4db 临时空文件
8f2b55aca176fb578cf39952f776c3da9aa50009 新增一行
e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git
现在我们尝试利用--hard 选项,回退到对应的提交。
[qcr@ecs-205826 project]$ git reset --hard e797df72c0669a2b81339ff4854328126d2e6c8e
HEAD is now at e797df7 一行你好,用于学习git
通过上述可以知晓回退成功了,接下来看看当前目录的文件。
[qcr@ecs-205826 project]$ ls
test.txt
然而发现,该目录下只剩了一个 test.txt 文件,而 test1、test2、test3 不存在了。原因就是,我们使用的--hard 选项,将工作区也回退了,而 test1、test2、test3 的提交是在回退的提交之后的,所以一旦回退到某一次提交,那么这次提交之后的所有提交都会被回退。
此时再打印 test.txt 中的内容可以发现。
[qcr@ecs-205826 project]$ cat test.txt
你好
回退成功,再打印 log 。
[qcr@ecs-205826 project]$ git log --pretty=oneline
e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git
发现我们回退的位子成为第一个了。
对应我们上一次查的log中,就会有被回退的提交(第一次回退的时候查找的log)。
[qcr@ecs-205826 project]$ git reset --hard 1bba34a04f231d8ad49f3ce5079879144cf6f4db
HEAD is now at 1bba34a 临时空文件
[qcr@ecs-205826 project]$ ls
test1 test2 test3 test.txt
[qcr@ecs-205826 project]$ git log --pretty=oneline
1bba34a04f231d8ad49f3ce5079879144cf6f4db 临时空文件
8f2b55aca176fb578cf39952f776c3da9aa50009 新增一行
e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git
可以发现全部回来了。而在这里有后悔药可以吃,是因为当前终端下并没有将之前的提交清除掉。
#:如果手残的在想回退的回退时已将上一次log的信息,因为 clear 而消失,是也可以查到之前的 log 信息的,使用 reflog 命令。
[qcr@ecs-205826 project]$ git log --pretty=oneline e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git [qcr@ecs-205826 project]$ git reflog e797df7 HEAD@{0}: reset: moving to e797df72c0669a2b81339ff4854328126d2e6c8e 1bba34a HEAD@{1}: reset: moving to 1bba34a04f231d8ad49f3ce5079879144cf6f4db e797df7 HEAD@{2}: reset: moving to e797df72c0669a2b81339ff4854328126d2e6c8e 1bba34a HEAD@{3}: commit: 临时空文件 8f2b55a HEAD@{4}: commit: 新增一行 e797df7 HEAD@{5}: reset: moving to e797df72c0669a2b81339ff4854328126d2e6c8e a8760f6 HEAD@{6}: commit: 新增一行 e797df7 HEAD@{7}: commit (initial): 一行你好,用于学习git
reflog 命令是用于记录本地的每一次的提交命令。
其就是对应提交的 commit ID,这么短是因为其就是 commit ID 的一部分,也是可以使用的。
[qcr@ecs-205826 project]$ git log --pretty=oneline e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git [qcr@ecs-205826 project]$ git reset --hard 1bba34a HEAD is now at 1bba34a 临时空文件 [qcr@ecs-205826 project]$ git log --pretty=oneline 1bba34a04f231d8ad49f3ce5079879144cf6f4db 临时空文件 8f2b55aca176fb578cf39952f776c3da9aa50009 新增一行 e797df72c0669a2b81339ff4854328126d2e6c8e 一行你好,用于学习git
这样的前提下,是因为我们能够找到 commit ID 才能吃后悔药,而在开发的过程中是会有很多的 Git 操作,是迟早会将我们的 commit ID 冲掉的。而一旦找不到 commit ID 就没有后悔药可以吃了。
值得说的是,Git 的版本回退速度非常快,因为 Git 在内部有个指向当前分支(此处是master)的 HEAD 指针, refs/heads/master 文件里保存当前 master 分⽀的最新 commit ID 。当我们在回退版本的时候,Git 仅仅是给 refs/heads/master 中存储⼀个特定的 version,可以简单理解成如下示意图:
所以版本回退其实就是将 master 中的 commit ID 进行改变,也就是将指向进行改变,于是版本的回退是十分的简单的、快速的。
如果我们在我们的工作区写了很长时间代码,越写越写不下去,觉得自己写的实在是垃圾,想恢复到上⼀个版本。
这个时候由于我们并没有进行 add 的操作,所以文件只存在于工作区中,在暂存区和版本库中是没有的,这个时候撤销的内容是工作区。
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
不喜欢这行想撤销
这个时候选择使用编辑文件,然后直接删除这一行,这个方法肯定是没有任何的问题的。但是如果写了很多了,这个时候一行一行的去删除很不现实,并且很容易就忘记了,并且不能保证手动删除的准确性!
Git 其实还为我们提供了更好的方式,我们可以使用 git checkout -- [file] 命令让工作区的文件回到最近⼀次 add 或 commit 时的状态。 要注意 git checkout -- [file] 命令中的 -- 很重要,切记不要省略,⼀旦省略,该命令就变为其他意思了!
[qcr@ecs-205826 project]$ git checkout -- test.txt
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
这个时候由于我们进行了 add 的操作,但是没有进行 commit 操作,所以文件只存在于工作区与暂存区中,在版本库中是没有的,这个时候撤销的内容是工作区、暂存区。
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
不喜欢这行想撤销
[qcr@ecs-205826 project]$ git add test.txt
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: test.txt
#
[qcr@ecs-205826 project]$ git reset HEAD test.txt
Unstaged changes after reset:
M test.txt
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes not staged for commit:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
这个时候我们就将暂存区中回退了,然后就是与情况一,一样的情况。
[qcr@ecs-205826 project]$ git checkout -- test.txt
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
这个时候由于我们进行了 add 和commit 的操作,所以文件存在于工作区、暂存区、版本库中,这个时候撤销的内容是工作区、暂存区、版本库。
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
不喜欢这行想撤销
[qcr@ecs-205826 project]$ git add test.txt
[qcr@ecs-205826 project]$ git commit -m "用于测试修改"
[master a2fd08f] 用于测试修改
1 file changed, 1 insertion(+)
不要担心,我们可以 git reset --hard HEAD^ 回退到上⼀个版本!不过,这是有条件的:就是你还没有把自己的本地版本库推送到远程仓库(commit之后对应push操作,后续再讲解)。撤销的目的就是:不影响远程仓库的代码。因为我们不知道远程仓库中对应的代码是怎么样的,所以我们想要撤退的操作的前提是远程仓库中确定没有这一份代码,才能真正意义上称作为撤销。
[qcr@ecs-205826 project]$ git reset --hard HEAD^
HEAD is now at 1bba34a 临时空文件
[qcr@ecs-205826 project]$ git status
# On branch master
nothing to commit, working directory clean
[qcr@ecs-205826 project]$ cat test.txt
你好
hello
工作区 | 暂存区 | 版本库 | 解决方式 |
---|---|---|---|
新文件 | 旧文件 | 旧文件 | 1、手动撤销 - 不推荐,易出错 2、git checkout -- [filename] |
新文件 | 新文件 | 旧文件 | git reset --mixed(变为上述情况) git reset --hard HEAD |
新文件 | 新文件 | 新文件 | 前提条件:commit之后没有进行push git reset --hard HEAD^ |
在 Git 中,删除也是⼀个修改操作,我们实战⼀下,如果要删除 test.txt 文件,怎么搞?
[qcr@ecs-205826 project]$ ls
test1 test2 test3 test.txt
[qcr@ecs-205826 project]$ rm test3
[qcr@ecs-205826 project]$ ls
test1 test2 test.txt
但这样直接删除是没有用的,反而徒增烦恼,其只是将我们对应的工作区中的内容进行删除,但是对于本地仓库是并没有删除的, git status 命令会立刻告诉你哪些文件被删除了:
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes not staged for commit:
# (use "git add/rm ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# deleted: test3
#
no changes added to commit (use "git add" and/or "git commit -a")
对第⼆种情况,很明显误删,需要使用 git 来进行恢复,很简单,我们刚学过:
[qcr@ecs-205826 project]$ git checkout -- test3
[qcr@ecs-205826 project]$ ls
test1 test2 test3 test.txt
[qcr@ecs-205826 project]$ git rm test3
rm 'test3'
[qcr@ecs-205826 project]$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# deleted: test3
#
可以看出对比前面,其即删除了工作区里的文件,也删除了暂存区中的文件,然后就只需要我们进行 commit 就可以了。
[qcr@ecs-205826 project]$ git commit -m "删除test3"
[master dccd2a4] 删除test3
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 test3