This is an explanation of the video content.
 用技术延续对ACG的热爱
38

 |   | 

git如何删除某一历史纪录

问题来源

在某次 push 时,突然发现将私密数据上传到了云端,于是立马对代码进行修改,重新 push。但是此时那些私密数据还是可以在 github 上的 commits 中看到,如下:

image.png

于是诞生了这么一个需求,我要如何删除这一次纪录呢?

画个图说明一下:

假如我的git历史记录是这样的:
1 --> 2 --> 3 --> 4 --> 5
 我要如何操作才能够变成这样:
1 --> 2     -->      4 --> 5
即我要如何删除 3 这一提交纪录,而不影响 4 和 5 的存在

经过我的测试,有两种方法不错,一个是将后面几个 commits 都删除,然后重新 commit。 还有一个是单独删除指定纪录。

下面新建了一个仓库,用来进行测试。

使用 reset 修改最近的几次提交

比如最近有这么几次提交:

$ git log --abbrev-commit --pretty=oneline
dcf851c (HEAD -> master, origin/master, origin/HEAD) 5
ffef0e1 4
58e834e 3
8a4d51b 2
230efb3 1
...

我们要删除 4 和 5 这两次提交(代码不会被删除),可以这样执行

git reset head~2

此处再输出一下提交历史,就会看到 45 的提交不见了

$ git log --abbrev-commit --pretty=oneline
58e834e (HEAD -> master) 3
8a4d51b 2
230efb3 1
...

此时 45 做过的修改,会作为“更改”存在,所以执行下面操作,就将 45 的更改内容作为一次新的提交,生成了新的历史

$ git add .
$ git commit -m "4+5"

再看看现在的历史记录

$ git log --abbrev-commit --pretty=oneline
12926ba (HEAD -> master) 4+5
58e834e 3
8a4d51b 2
230efb3 1
....

接下来将修改推送到远程仓库,需要注意此时要使用强制推送,毕竟我们都删除了两次历史记录了(注意此仓库是个人仓库,所以不会影响到他人)

git push -f
# -f 也可以换成 --force-with-lease,不过在这里都一样(因为仓库是我个人的)

可以看到,远程仓库的历史记录确实不见了,并且 commits 少了一次

image.png image.png

image.png image.png

使用 rebase 删除指定

在此之前,先看看我模拟的场景

image.png

image.png

image.png

现在要解决的,就是要删除指定历史记录,从而看不到私密数据

首先获取历史记录

$ git log --abbrev-commit --pretty=oneline
d7544dc (HEAD -> master, origin/master, origin/HEAD) 从历史记录中删除私密数据
553ae7c 日常完成任务
b9c7b36 日常完成任务
2a07574 日常完成任务
7a5387d 日常完成任务
dbd5dda 修改删除了代码中的私密数据(但此时还未注意到可以从历史记录中看到私密数据)
deac60d 2(此处可看到私密数据)
75125bf 1

我们要删除的是 2 这一个记录,于是复制此记录前面的 commit id: deac60d

然后执行 rebase
(注意: 此时会进入 rebase 状态,如果想反悔,可以执行 git rebase --abort

git rebase -i deac60d^
# 注意此处有个 ^(^ 表示 前一次提交)

此处会打开 git-rebase-todo 文件,如图所示(vscode):

image.png

pick deac60d 2(此处可看到私密数据) 整行删除,如图:

image.png

然后关闭该文件,此时会有冲突需要我们合并

image.png

我这里解决冲突,直接点击 Accept Incoming Change

image.png

(可选)此时可以先看看历史纪录是什么样的:

$ git log --abbrev-commit --pretty=oneline
75125bf (HEAD) 1

(可选)不要忘记了现在是在 rebase 状态下:

$ git status
interactive rebase in progress; onto 75125bf
Last command done (1 command done):
   pick dbd5dda 修改删除了代码中的私密数据(但此时还未注意到可以从历史记录中看到私密数据)
Next commands to do (5 remaining commands):
   pick 7a5387d 日常完成任务
   pick 2a07574 日常完成任务
  (use "git rebase --edit-todo" to view and edit)
You are currently rebasing branch 'master' on '75125bf'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
        both modified:   readme.md

从 vscode 底部可以直接看到:image.png

处理完冲突后,直接提交,备注信息可以改也可以不改,我这里就修改一下吧,把括号内容删除了。弄完备注信息后关闭文件即可

$ git commit -a
hint: Waiting for your editor to close the file... # 修改备注后关闭文件

image.png

image.png

好了,到这里,我们就成功完成了我们的目标了 —— 删除某一历史记录。

(可选)此时可以再看一下我们的状态是什么样的

$ git status
interactive rebase in progress; onto 75125bf
Last command done (1 command done):
   pick dbd5dda 修改删除了代码中的私密数据(但此时还未注意到可以从历史记录中看到私密数据)
Next commands to do (5 remaining commands):
   pick 7a5387d 日常完成任务
   pick 2a07574 日常完成任务
  (use "git rebase --edit-todo" to view and edit)
You are currently editing a commit while rebasing branch 'master' on '75125bf'.
  (use "git commit --amend" to amend the current commit)
  (use "git rebase --continue" once you are satisfied with your changes)

nothing to commit, working tree clean

后面的工作没什么了,就是保存我们的修改并退出 rebase 状态。

git rebase --continue  # 因为只是想要删除某一个历史记录,所以这里使用的是 --continue

image.png 可以看到此时又有一个冲突,简单合并(Accept Incoming Change)一下就可以了,然后继续:

$ git commit -a
hint: Waiting for your editor to close the file... 

image.png

此处又要我们修改备注信息, 这次我们就不修改了, 直接关闭文件就行

image.png

后面的基本就是重复工作了

git rebase --continue
# (如果有冲突就)合并冲突, 并提交
git commit -a
# 弄备注, 弄完后继续
git rebase --continue
...

如果你修改的内容不多,那么在某次 git rebase --continue 后,若没有冲突发生,则 git 会直接回到最新的状态(即退出 rebase 操作)。比如我这里处理完两次冲突后,就可以直接退出了

$ git rebase --continue
readme.md: needs merge
You must edit all merge conflicts and then
mark them as resolved using git add
[detached HEAD 9deb8f4] 日常完成任务
 1 file changed, 4 insertions(+)
error: could not apply b9c7b36... 日常完成任务
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply b9c7b36... 日常完成任务
Auto-merging readme.md
CONFLICT (content): Merge conflict in readme.md
$ git commit -a
[detached HEAD e7123cd] 日常完成任务
 1 file changed, 1 insertion(+), 3 deletions(-)
# 就是这里处理完冲突后,再执行下面的语句,就可以直接退出 rebase 状态了 
$ git rebase --continue
Successfully rebased and updated refs/heads/master.
# 现在让我们看看历史纪录, 可以看到纪录 2 已经不见了, 同时后续的 commit id 也变了
$ git log --abbrev-commit --pretty=oneline
bbd6e4e (HEAD -> master) 从历史记录中删除私密数据
9464717 日常完成任务
e7123cd 日常完成任务
9deb8f4 日常完成任务
6025f93 日常完成任务
11161ae 修改删除了代码中的私密数据
75125bf 1

好了,到这里本地的工作就全部完成了,现在我们再将本地的分支强制推送到云端就可以了

git push --force-with-lease
# 如果仓库是多人协作,则使用 --force-with-lease 比 -f 更安全一点。

推送成功后,现在来看看云端的情况

image.png

image.png

可以看到成功的删除了纪录 2

最后,rebase 还有很多好用的地方,比如在这里我是直接删除掉 git-rebase-todo 文件中的某一纪录, 其实也可以将里面的 pick 换成 edit, 那么就可以修改某一纪录的数据的。我在书写这个测试案例的时候,就用过一次了,不过因为要每一步都纪录下来,然后弄成笔记的话,属实是“性价比”不高(😁😀,真的不是我懒😉),毕竟现在我基本懂了使用方法,也动手试过了一遍。后续我只需要再看看这一篇基本就会回想起来了。所以有兴趣的话,可以自己去试试,把 pick 换成 edit

总结

  • 注意私密数据,最好放在特定的位置(记得将该位置添加进 .gitignore),然后通过“导入”的方式使用私密数据。

  • 建议每次 commit 后,不要总想着立马就 push(我就是这样),这样,当你发现错误时,只需要修改本地纪录,而不需要修改云端纪录。

  • git 有一个准则

    Do not rebase commits that exist outside your repository and that people may have based work on.

    If you follow that guideline, you’ll be fine. If you don’t, people will hate you, and you’ll be scorned by friends and family.

    来源

    翻译:

    一旦分支中的提交纪录发布到云端公共仓库,那么不要执行变基。

    如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

转载自https://juejin.cn/post/7108585978327105566

38 开发工具 ↦ Git疑难杂症 __ 773 字
 Git疑难杂症 #5