Git 错误提交后该如何回滚操作

1. Git 架构

git

  • Workspace:工作区(当前用户操作修改的区域)
  • Index / Stage:暂存区 (add 后的区域)
  • Repository:仓库区或本地仓库(commit 后的区域)
  • Remote:远程仓库(push 后的区域)

Git核心概念

整体过程可以简述为:

  • 工作区–>add–>暂存区–>commit–>本地仓库区–>push–>远程仓库区
  • 远程仓库区–>fetch–>使用 refs\remotes 下对应分支文件记录远程分支末端 commit_id 和 本地仓库区 -->merge–>工作区
  • 远程仓库区–>pull–>使用 refs\remotes 下对应分支文件记录远程分支末端 commit_id and 本地仓库区 and 工作区

具体的 git 的组成部分和概念命令,请移步下述两个博客(超链接):
Git 技术干货!工作中 Git 的使用实践和常用命令合集!
Git - 使用 git 不知道内部实现机制怎么行

新建一个 Git 仓库,在 Git 仓库中可逻辑上划分三个区域——工作目录、暂存区、版本库,这三个区域是抽象的,Git 仓库中以文件来记录各个区域的内容。

Git 会追踪工作目录里文件及内容的变更。当我们在 Git 仓库的工作目录下新建三个文件,Git 会将新建的文件视作变更(图中五角星表示被 Git 检测到更改)。

我们想将 html 文件提交到仓库中,首先将变更(新建的 HTML )从工作目录添加到暂存区。然后将这个文件从暂存区提交到版本库。这时才算拍摄了一组快照,提交的更改不会丢失。

图1

当我们在工作目录下修改了一个文件, Git 仓库会识别文件修改部分(标记 2 颗星),然后我们将文件修改添加到暂存区,最后将文件修改提交到版本库。

图2

修改 CSS 文件,将修改添加到暂存区,再提交到仓库。

问题:如果我们在工作目录下第一次修改一个文件,并将它添加到暂存区,然后我们第二次修改文件,第二次修改没有添加到暂存区,最后我们将文件 commit 到版本库,请问仓库的文件内容是第几次修改的?请思考。

如果第一次修改 CSS 文件,添加到暂存区,再第二次修改 CSS 文件,现在执行 commit,提交到版本库只有第一次修改的内容。

图3
当执行 commit 时,只有暂存区中的内容会被提交到版本库。

如果我们想要将第二次修改也 commit 到版本库,我们必须先将第二次修改先添加到暂存区,第一次修改的暂存与第二次修改的暂存合并,然后执行 commit,仓库中就会有第一次和第二次修改的内容。

图4
添加到暂存区的内容会合并,一起提交到仓库。

第一次修改 CSS 文件,添加到暂存区,但没有提交仓库;第二次修改 CSS 文件,添加到暂存区,会与第一次的修改合并,这时提交到版本库的内容是两次修改的合并。

2. 实践

假设项目存在这么一个提交记录:

$ git log
commit commit_id4 (HEAD -> master)
Author: test
Date:   Thu Aug 20 16:28:45 2020 +0800
    第三次修改 README 文件
commit commit_id3 (HEAD -> master)
Author: test
Date:   Thu Aug 20 16:28:45 2020 +0800
    第二次修改 README 文件
commit commit_id2
Author: test
Date:   Thu Aug 20 16:28:19 2020 +0800
    第一次修改 README 文件
commit commit_id1
Author: test
Date:   Thu Aug 20 16:26:59 2020 +080
    初始化项目

提交顺序为:commit_id1 --> commit_id2 --> commit_id3 --> commit_id4
注意:在 git 中每次的 commit 都有一个 commit id 唯一标识当前的提交!

下面,我们先来解决小明的这个问题,使用git reset即可完美解决~

3. 问题解决

洋仔:小明,你的这个就可以用git reset 这个命令来完美的搞定,下面我们看一下如何解决

1、获取当前提交的 commit id
命令:git log
获取到当前项目分支下的所有 commit 记录;
假设上述小明提交错误的 commit id 为commit id:commit_id4这一次提交;
他的上一次提交就是commit id:commit_id3 ,我们要将修改回滚到commit_id3的时刻!

小明:我想要把我刚才 commit 的修改保留下来,我修改的代码不能给我删除掉呀!
洋仔:没问题

2、将某个 commit id 前的 commit 清除,并保留修改的代码
命令:git reset <commit_id> 当前场景下就是:git reset commit_id3
将指定 commit_id 后的所有提交,都去除,并保留修改的代码在本地的区域,也就是Workspace

小明:啊哈,这样的话我就可以把错误代码修改后再提交了; 但是我已经 push 到线上仓库的数据怎么办呢?
洋仔:别急,有办法~

3、修改代码完成后,将修改好的代码 add 到暂存区,并提交到本地仓库中
命令:git add <file_name> and git commit
当前场景下:git add . and git commit 将最新修改后的代码 commit 则提交后的提交记录假设如下:

可以看到,我们错误提交的commit_id4提交记录消失,取而代之的是我们更新代码后提交的记录commit_id5; 这样就完成了本地的代码修改和更新

$ git log
commit commit_id5 (HEAD -> master)
Author: test
Date:   Thu Aug 20 16:28:45 2020 +0800
    第三次修改 README 文件-更新错误后提交
commit commit_id3 (HEAD -> master)
Author: test
Date:   Thu Aug 20 16:28:45 2020 +0800
    第二次修改 README 文件
commit commit_id2
Author: test
Date:   Thu Aug 20 16:28:19 2020 +0800
    第一次修改 README 文件
commit commit_id1
Author: test
Date:   Thu Aug 20 16:26:59 2020 +080
    初始化项目

整体流程如下:

git log
git reset commit_id3
修改代码
git add .
git commit -m '第三次修改 README 文件-更新错误后提交'

洋仔:好了,小明,你的问题完美解决了
小明:哦吼,但是我还有一个问题: 如果我想要不保留回滚 commit 的修改,直接删除掉修改!该怎么处理呢?
洋仔:简单~ 我们整体看一下 git reset 命令

3.1 后悔药 git reset

在进行下面的讲解是,还是先假设有这么一个提交链:
commit_id1 --> commit_id2 --> commit_id3 --> commit_id4

git reset <param> commit_id2

reset 是将 HEAD 重新定位到commit_id2上,对于 commit_id3commit_id4 和本地当前的修改,对于不同的参数 param,会有不同的处理:

reset 命令有三种处理模式:

  • –soft:保留 commit 修改,将修改存储到 index 中;也就是说 git add 后的区域
  • –mixed:保留 commit 修改,将修改存储到本地工作区域中;也就是说 git add 前的区域
  • –hard:删除 commit 修改,慎用!
  1. git reset --soft
    回滚 commit_id 前的所有提交,不删除修改:
    git reset --soft commit_id
    重设 head,不动 index,所以效果是 commit_id 之后的 commit 修改全部在 index 中 将 id3 和 id4 的修改放到 index 区(暂存区),也就是 add 后文件存放的区域,本地当前的修改保留

    git reset --soft SHA 会将对应的 commit 更改回退到暂存区:
    soft

  2. git reset --mixed
    回滚 commit_id 前的所有提交,不删除修改:git reset commit_id 等同于 git reset --mixed commit_id 与下述的 git reset --hard commit_id 效果不同。
    重设 head 和 index,不重设 work tree,效果就是 commit_id 之前的修改,全部在 work tree 中,为还未 add 的状态 将 id3 和 id4 的所有修改放到本地工作区中,本地当前的修改保留。

    git reset --mixed SHA 会将对应的 commit 更改回退到工作目录:

    mix

  3. git reset --hard
    回滚 commit_id 前的所有提交,将修改全部删除:git reset --hard commit_id
    重设 head、index、work tree,也就是说将当前项目的状态恢复到 commit_id 的状态,其余的全部删除(包含 commit_id 后的提交和本地还未提交的修改) 慎用!!

    git reset --hard SHA 会将对应的 commit 直接删除,无法恢复。

    hard

    git reset --hard commit_id 该命令主要是用于代码回退,仅对已经 commit 到本地的代码有效。通过 git reflog 命令查看之前版本 id 信息。

    wohu@ubuntu-dev:~/git_demo/test_demo$ git reflog
    eda37f0 HEAD@{
          
          0}: commit: third commit
    8d2efe6 HEAD@{
          
          1}: commit: second commit
    14b5e12 HEAD@{
          
          2}: commit (initial): first commit
    wohu@ubuntu-dev:~/git_demo/test_demo$ cat README.md 
    - first commit
    - second commit
    - third commit
    wohu@ubuntu-dev:~/git_demo/test_demo$ 
    

    想再次切回到注释为 second commit 版本下,可以再次通过 git reset --hard 8d2efe6 进行版本切换。

    wohu@ubuntu-dev:~/git_demo/test_demo$ git reset --hard 8d2efe6
    HEAD is now at 8d2efe6 second commit
    wohu@ubuntu-dev:~/git_demo/test_demo$ ls
    README.md
    wohu@ubuntu-dev:~/git_demo/test_demo$ cat README.md 
    - first commit
    - second commit
    
    wohu@ubuntu-dev:~/git_demo/test_demo$ 
    

3.2 后悔药 git revert

小明:原来 git reset 这么强大呀! 但是我这还有个问题:

如果想要只操作修改中间的一个 commit,不对其他的 commit 产生影响; 也就是类似于我们只修改 commit_id2,而对 commit_id3 和 commit_id4 无影响,该怎么处理呢?

洋仔:(这么多问题,幸亏我懂,要不这次就丢大了。。) 简单! git revert 命令!

适用场景: 在项目开发中,突然发现在前几次的提交中,有一次提交中包含一个 bug; 当然我们可以进行一个新的修改,然后再提交一次; 但是,不优雅哈哈; 我们可以直接重做有 bug 的 commit~

为什么不直接去再添加一个 commit 呢?
git revert 是用于“反做”某一个版本,以达到撤销该版本的修改的目的。
比如,我们 commit 了三个版本(版本一、版本二、 版本三),突然发现版本二不行(如:有 bug),想要撤销版本二,但又不想影响撤销版本三的提交,就可以用 git revert 命令来反做版本二,生成新的版本四,这个版本四里会保留版本三的东西,但撤销了版本二的东西;

在 revert 命令中常用的就两个:

  • git revert -e <commit_id>:重做指定 commit 的提交信息
  • git revert -n <commit_id>:重做执行 commit 的代码修改
  1. git revert -e
    重做 commit_id 的提交信息,生成为一个新的 new_commit_id
    git revert -e commit_id

  2. git revert -n
    重做 commit_id 的提交git revert -n commit_id将 commit_id 中修改,放到 index 区,我们可以对他重新做修改并重新提交

git revert <commitHash>  回退指定提交记录

git revert --no-commit <commitHash> 
回退指定提交记录 --no-commit 不会自动提交成一条 `commit` 后续手动操作

git revert --no-commit <commitHash1> <commitHash2>  
回退多个提交记录

git revert --no-commit <commitHash1>..<commitHashN>
回退一定范围的提交记录,前开后闭(不包含 1,包含 N)

3.3 revert vs reset

  • git revert 是用一次新的 commit 来回滚之前的 commit,此次提交之前的 commit 都会被保留不动;
  • git reset 是回到某次提交,提交及之前的 commit 都会被保留,但是此 commit id 之后的修改都会被删除或放回工作区等待下一次提交;

小明:还有这种操作,可以直接单独操作提交过程中的某一个 commit! 太棒了!

4. 后悔药git checkout

小明:还有最后一个问题:
如果我在一次开发中,发现某个文件修改错误了,想要将文件恢复到刚 pull 代码时的状态怎么办呢?
洋仔:简单! 看 git checkout 解决这个问题!

我们知道使用git checkout可以

  • git checkout <branch_name>切换分支

  • git checkout -b <branch_bame>创建分支等操作

它还有回滚指定文件的修改的功能,命令:git checkout -- <file_name>
上述语句的作用,就是将 file_name 的本地工作区的修改全部撤销,有两种情况:

  • 如果 file_name 在 commit 后没有 add 过这个文件,则撤销到版本库中的状态

  • 如果 file_name 在 commit 后 add 过这个文件,则撤销到暂存区的状态,也就是 add 后的状态

总之,就是让指定的文件回滚到最近的一次git add 或者 git commit时的状态!

git checkout --file 该命令是对未提交到缓存区的代码进行撤销。创建空文件 Readme.md。通过 git status 查看文件在工作区的状态。

wohu@ubuntu-dev:~/git_demo/test_demo$ echo "hello world" >> Readme.md 
wohu@ubuntu-dev:~/git_demo/test_demo$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   Readme.md

no changes added to commit (use "git add" and/or "git commit -a")
wohu@ubuntu-dev:~/git_demo/test_demo$ 

然后执行 git checkout -- Readme.md。我们又回复到了修改前的版本。

wohu@ubuntu-dev:~/git_demo/test_demo$ cat Readme.md 
hello world
wohu@ubuntu-dev:~/git_demo/test_demo$ git checkout -- Readme.md
wohu@ubuntu-dev:~/git_demo/test_demo$ cat Readme.md 
wohu@ubuntu-dev:~/git_demo/test_demo$ 

我们再次对 Readme 内容进行编辑,并将其放入到缓存区中。

wohu@ubuntu-dev:~/git_demo/test_demo$ echo "hello world" >> Readme.md 
wohu@ubuntu-dev:~/git_demo/test_demo$ git add Readme.md 
wohu@ubuntu-dev:~/git_demo/test_demo$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   Readme.md

wohu@ubuntu-dev:~/git_demo/test_demo$ git checkout -- Readme.md
wohu@ubuntu-dev:~/git_demo/test_demo$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   Readme.md

wohu@ubuntu-dev:~/git_demo/test_demo$ 

此时执行 git checkout 没有啥效果,如果想恢复就要用到我们接下来介绍的命令。

git reset HEAD -- file 该命令是将放入暂存区的代码进行撤销,放入到工作区中。

wohu@ubuntu-dev:~/git_demo/test_demo$ git reset HEAD -- Readme.md
Unstaged changes after reset:
M	Readme.md
wohu@ubuntu-dev:~/git_demo/test_demo$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   Readme.md

no changes added to commit (use "git add" and/or "git commit -a")
wohu@ubuntu-dev:~/git_demo/test_demo$ 

这样我们的 Readme.md 代码又再次回到了工作区。再次进行 git checkout 将代码内容进行恢复。

参考:
https://gitbook.cn/books/5f4db870237b0e7d7f238fa0/index.html
https://gitbook.cn/books/5ebca21b9b907c12334b6287/index.html

猜你喜欢

转载自blog.csdn.net/wohu1104/article/details/114645119