工作区域

  • Workspace本地文件系统上的一个目录,包含了从 Git 仓库中检出的项目文件。可以在这里查看、编辑和运行项目。

  • Index / Stage暂存区是一个位于工作目录和仓库之间的临时区域。当我们对工作目录中的文件做出修改后,可以将这些修改添加到暂存区,以准备提交到仓库。

  • Repository:本地仓库,就是安全存放数据的位置,这里面有你提交到所有版本的数据。其中 HEAD 指向最新放入仓库的版本

  • Remote:远程仓库,托管代码的服务器,可以简单的认为是你项目组中的一台电脑用于远程数据交换

代码回滚

git reset

假如我们的系统现在有如下几个提交:

其中:AB 是正常没有问题的提交,CD 是存在错误的提交。现在,我们想把 CD 回退掉。此时,HEAD 指针指向 D 提交(6tgh8)。我们只需将 HEAD 指针移动到 B 提交(a3456),就可以达到目的。

这个时候我们就可以使用 git 的 reset 命令来进回滚:

git reset --hard a3456

reset 的模式有四种:

  • soft:将本地仓库回退到指定版本,暂存区和工作区不变。也就是说新增和修改的记录都会保留,后面还可以提交。

  • mixed:将本地仓库回退到指定版本,暂存区清空,工作区不变。也就是说新增的文件会取消 add(在 idea 里表现就是这个文件变“红”了,需要重新 add 才能提交),修改的记录保留,后面还可以提交。

  • hard:将本地仓库回退到指定版本,暂存区和工作区清空。相当于是丢弃所选提交后的所有提交的内容。

  • keep将版本库回退的同时,将暂存区也清空,但工作区中文件如果当前版本和退回版本之间没有发生过变动,则工作区的修改保持不变;如果发生了变动,并且工作区也进行了修改,需要自行合并或解决冲突

命令运行后,我们系统的版本如下:

我们在运行上面的命令后,修改的只是本地仓库,远程仓库的 HEAD 指针依然不变,仍在 D 提交上。所以,如果直接使用 git push 命令的话,将无法将更改推到远程仓库,会被远程拒绝。此时,可以使用 -f / -force 选项将提交强制推到远程仓库:

git push -f

采用这种方式回退代码的弊端显而易见,那就是会使 HEAD 指针往回移动,从而会失去之后的提交信息。

在这里提醒大家:不要轻易使用 git push -f,不要轻易使用!!!

会使用到强推可能是因为有一些不可描述的代码被误提交,希望这种不堪入目的代码永远消失在历史的长河中,于是,掏出百度查到了可以使用 git reset 回滚代码,然后使用 git push -f 强推到远程仓库,以为这样可以悄无声息的丢弃掉这段代码,殊不知,只要修改被提交,git 就已经将信息记录了下来,那段代码将会永永远远在远程仓库里烙下印记。

所以,再次提醒大家,不要轻易使用 git push -f !!!

而且,有些公司明令禁止使用 git reset 命令去回退代码。git reset 会引发多种问题,在多人协作的分支上,使用强推来回滚代码,会使其他人辛辛苦苦的代码丢失。

git revert

git revert 的作用通过反做创建一个新的版本,这个版本的内容与我们要回退到的目标版本一样,但是 HEAD 指针是指向这个新生成的版本,而不是目标版本。

简单来说就是我们提交了一个对于要回滚的提交的反向操作,来做到代码的回滚。

使用 git revert 命令来实现上述例子的话,我们可以这样做:先 revert D,再 revert C(有多个提交需要回退的话需要由新到旧进行 revert):

git revert 6tgh8
git revert d5rg7

这里会生成两个新有提交:D'C',如下图示:

这里只有两个提交需要 revert,我们可以一个个回退。但如果有几十个呢?一个个回退效率太低而且容易出错。我们可以使用以下方法进行批量回退:

git revert OLDER_COMMIT^..NEWER_COMMIT

这时,错误的提交 CD 依然保留。而且,这样操作的话 HEAD 指针是往后移动的,可以使用 git push 命令推送到远程仓库里。这种做法是被企业所鼓励的。

再举一个例子:

假如现有三个提交,有问题的提交位于中间位置。如下图示:

这时,直接使用 git reset 命令将 HEAD 指针重置到 A 提交显然是不行的,因为 C 提交是没问题的,需要保留。我们的做法是:先把 C 提交 及 B 提交全部回退,再使用 cherry-pick 命令将 C 提交重新再生成一个新的提交 C'',这样就实现了将 B 提交回退的需求。完整的过程如下:

通过以上对比可以发现,git resetgit revert 最大的差别就在于,git reset 会失去后面的提交,而 git revert 是通过反做的方式重新创建一个新的提交,而保留原有的提交。