背景 1
在一个分支上的提交顺序如下:-> 代表新的提交
- 在提交 E 中,文件包含了 GitHub 生成的 token
- 提交 F 是一次普通的提交,不包含 token
A -> ... -> E -> F (敏感信息在 E 中)
附:给提交起名是为了方便说明问题。在 Git 中是用 Hash 值来表示一个提交的。
准备工作
在 GitHub 生成 1 个 token,用于复现 bug。
rebase_token:
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
解决方案
1、根据控制台错误信息或查看提交日志(git log --oneline),确定包含 token 的第一次提交。
2、启动交互式 rebase,在编辑器中将第一个包含 token 的提交的状态由 pick 修改为 edit。
3、在停止状态下,删除提交中包含的 token。
4、(1 ) 将变更添加到暂存区(git add .);(2) 将这次修改作为新的提交(git commit --amend),并覆盖之前的提交。
5、完成 rebase(git rebase --continue),会再次进入编辑器模式,提示为新提交提供 commit message。
6、提供提交信息后,由于后续的提交都需要基于当前的新提交,可能会有冲突,需要先解决冲突。解决冲突也会发生变基。最后,完成 rebase,push 代码到 GitHub。
bug复现&解决
1、进行两次提交
提交 E:在提交的文档中插入一个 token,用来演示后续的 push 操作被 GitHub 拒绝
提交 F:提交 F 相较于提交 E 是增量修改。
F 提交完以后,要将本地代码 push 到 GitHub,push 被 GitHub 拒绝:
push 被 GitHub 拒绝:
在控制台查看细节,如下:
14:30:47.941: [JavaWiki] git -c credential.helper= -c core.quotepath=false -c log.showSignature=false push --progress --porcelain origin refs/heads/master:master
Enumerating objects: 11, done.
Counting objects: 9% (1/11)
Counting objects: 18% (2/11)
Counting objects: 27% (3/11)
Counting objects: 36% (4/11)
Counting objects: 45% (5/11)
Counting objects: 54% (6/11)
Counting objects: 63% (7/11)
Counting objects: 72% (8/11)
Counting objects: 81% (9/11)
Counting objects: 90% (10/11)
Counting objects: 100% (11/11)
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 12% (1/8)
Compressing objects: 25% (2/8)
Compressing objects: 37% (3/8)
Compressing objects: 50% (4/8)
Compressing objects: 62% (5/8)
Compressing objects: 75% (6/8)
Compressing objects: 87% (7/8)
Compressing objects: 100% (8/8)
Compressing objects: 100% (8/8), done.
Writing objects: 12% (1/8)
Writing objects: 25% (2/8)
Writing objects: 37% (3/8)
Writing objects: 50% (4/8)
Writing objects: 62% (5/8)
Writing objects: 75% (6/8)
Writing objects: 87% (7/8)
Writing objects: 100% (8/8)
Writing objects: 100% (8/8), 866 bytes | 866.00 KiB/s, done.
Total 8 (delta 6), reused 0 (delta 0), pack-reused 0 (from 0)
remote: Resolving deltas: 0% (0/6)
remote: Resolving deltas: 16% (1/6)
remote: Resolving deltas: 33% (2/6)
remote: Resolving deltas: 50% (3/6)
remote: Resolving deltas: 66% (4/6)
remote: Resolving deltas: 83% (5/6)
remote: Resolving deltas: 100% (6/6)
remote: Resolving deltas: 100% (6/6), completed with 3 local objects.
remote: error: GH013: Repository rule violations found for refs/heads/master.
remote:
remote: - GITHUB PUSH PROTECTION
remote: —————————————————————————————————————————
remote: Resolve the following violations before pushing again
remote:
remote: - Push cannot contain secrets
remote:
remote:
remote: (?) Learn how to resolve a blocked push
remote: https://docs.github.com/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push
remote:
remote:
remote: —— GitHub Personal Access Token ——————————————————————
remote: locations:
remote: - commit: 5b8f49aca4ce563a463a6750222692724ae2f0f7
remote: path: 玩转MacBook/PicGo + Typora + GitHub搭建图床.md:112
remote: - commit: a6d81c0aff0efc8b8a8db1b8e6a2d731dd33820c
remote: path: 玩转MacBook/PicGo + Typora + GitHub搭建图床.md:112
remote:
remote: (?) To push, remove secret from commit(s) or follow this URL to allow the secret.
To github.com:Acura-bit/JavaWiki.git
remote: https://github.com/Acura-bit/JavaWiki/security/secret-scanning/unblock-secret/2qph1YYqOWV1XcOtE5UkX42tUh2
! refs/heads/master:refs/heads/master [remote rejected] (push declined due to repository rule violations)
remote:
remote:
Done
remote:
error: failed to push some refs to 'github.com:Acura-bit/JavaWiki.git'
由标红处可以看到,GitHub 拒绝 push 的原因是提交中包含 secret,也就是 token。标绿处提示我们移除 token 后再尝试提交。
思考:我们检查代码,然后把代码中的 token 删掉后再提交、再 push,能通过吗?
- 答案是不可以。因为报错信息明确要求我们要将提交历史中包含的 token 移除,因此需要修改之前的提交。
这时候,rebase 就要登场了。
2、定位第一次包含 token 的提交
控制台的报错信息给出了包含 token 的两次提交:
在提交 E 中包含 token,因为提交 F 是基于提交 E 创建的,所以 F 中也被认为含有 token。但由于 F 是在 E 的基础上做了增量修改,所以在变基编辑时只需要修改提交 E。
其实在背景中已经说明了,提交 E 是第一次包含 token 的提交。问题是控制台显示的两个提交那个是第一次提交呢?
查看提交日志:git log --oneline
由提交日志可知,提交 E 的哈希值为 a6d81c0;提交 F 的哈希值为 5b8f49a。
rebase_token__169">3、启动交互式 rebase,列出所有包含 token 的提交
执行如下命令,从提交 E 的前一个提交开始 rebase。之所以从前一次开始,是为了包含提交 E。
git rebase -i a6d81c0~1
执行命令后,IDEA 的终端进入了交互时状态,如下图所示:
4、修改有问题的提交
将目标提交 E 从 pick
改为 edit
,然后保存:
- 英文输入模式下,输入 i 进入 insert 模式
- 将第一行的 pick 修改为 edit
- 按 ESC,退出 insert 模式
- 输入 :wq,然后回车
将提交 E 由 pick 状态修改为 edit 状态:
当前,控制台打印如下:
这样,Git 会暂停在这个提交上,让你对其内容进行修改。如下所示:
这时,我们记录一下提交 E 和 F:
(1) 提交 E 的现状:
(2) 提交 F 的现状:
开始编辑,如下图所示,当前确实暂停在了提交 E 上:
删除代码中的 token:
5、提交修改
(1) 修改完成后,将改动添加到暂存区:
git add .
将当前修改(删除了 token)提交以覆盖之前的提交:
git commit --amend
执行上述两步后,控制台打印如下:
rebase_247">6、完成 rebase
输入命令 git rebase --continue
,完成 rebase:
如下所示,终端又进入了编辑器模式:在上面的变基编辑(删除 token)后,执行 git commit --amend 是为了覆盖提交 E,所以会出现如下界面,提示你修改 commit message,当然也可以不用修改。
这里我们使用全新的 commit message,进行如下操作:
- 英文输入模式下,输入 i 进入 insert 模式
- 修改提交说明
- 按 ESC,退出 insert 模式
- 输入 :wq,然后回车
如上图所示,在编辑器中修改了 commit message。
但是,退出编辑器,提交 E 被全新的提交 G 覆盖,提交 G 的哈希值为 7f41eb9。然而,发生了冲突,
冲突原因:在提交 E 中编辑(删除 token)后,使用 git commit --amend 覆盖提交 E,生成了新的提交 G,如上图粉色框。因为,提交 F 是基于原来的提交 E 的,在 E 被 G 覆盖后,需要基于 G。然而,不能直接将 G 的内容应用于 F 如上绿色框,因为缺少内容。如下图所示:
解决冲突:
将修改添加到暂存区 git add .
,然后执行 git rebase – continue,弹出了终端编辑模式:
7、将最新的提交push到GitHub
执行 push 操作时可以看到,原来的提交 E 和 F 都发生了编辑,都被新的提交覆盖了:
查看提交状态:
(1) 提交 E 被新的提交 7f41eb9f(G)覆盖:因为提交 E 相较于之前的提交只是新增了几行,所以在变基编辑(删除)后,新的提交没有相较于之前的更改。
(2) 提交 F 被新的提交 a594d2f0(H)覆盖了
思考
在背景 1 中,E 是包含 token 的第一个提交,提交 F 没有改动提交 E 关于 token 的内容,只是另外添加了一些内容。我们在交互式变基时,只修改了提交 E 的内容,而没有修改提交 F 的内容,这是为啥呢??
A -> ... -> E -> F (敏感信息在 E 中)
解释:
Git 的提交记录是一个链式的结构,每次新的提交都会基于上一提交的快照创建。提交 F 本身只记录了在提交 E 的基础上新增或修改的内容,但并不会重复记录提交 E 的全部内容。
- 如果提交 F 本身没有显式地重新添加该 token,则 token 实际上仍是提交 E 中的遗留问题。
- 当通过
rebase
修改提交 E 时,Git 会重新生成基于修改后提交 E 的新历史,而提交 F 也会被重新基于这次修改后的记录进行构建。
举个例子:
假设有以下提交记录:
原始提交记录:
A: 初始化项目
B: 不小心加入了 token
C: 修改代码
提交 B 的内容:
新增 README.md:
- API 文档说明
- token = "abc1234"
提交 C 的内容:
修改 README.md:
- 调整 API 的描述文案
如果只清理提交 B 中的 token,Git 在重构历史时会自动保证提交 C 基于清理后的提交 B:
清理后记录:
A: 初始化项目
B': 删除 token(修订的提交 B)
C': 修改代码(基于提交 B' 重构)
提交 B’ 的内容:
新增 README.md:
- API 文档说明
提交 C’ 的内容,或者说相对于提交 B’ 不同的内容:
修改 README.md:
- 调整 API 的描述文案
由于 token 已在 B’ 中被移除,C’ 不会再继承该敏感信息。
背景 2
由上面的思考引出了下面的新问题。
在一个分支上的提交顺序如下:-> 代表新的提交
- 在提交 E 中,文件包含了 GitHub 生成的 token
- 在提交 F 中,同样包含 GitHub 生成的 token
A -> ... -> E -> F (敏感信息在 E 中)
解决方案
1、逐一 rebase:可以参考背景 1 中的解决方案,先解决提交 E;然后,以同样的方式解决提交 F。
2、一同 rebase。(演示这种方案)
bug 复现&解决
1、进行两次提交
提交 E:在提交的文档中插入一个 token,用来演示后续的 push 操作被 GitHub 拒绝
提交 F:在另一份文档中添加 token
效果:提交 E 中含有 token;提交 F 相较于 E,除了含有 E 添加的 token 外,还含有自身添加的其他 token。
F 提交完以后,要将本地代码 push 到 GitHub,push 被 GitHub 拒绝:
在控制台找到被拒绝 push 的提交的信息:
2、定位第一次包含 token 的提交
查看日志:
第一个包含 token 的提交的哈希值为 5961957。
rebase_token__426">3、启动交互式 rebase,列出所有包含 token 的提交
执行如下命令,从提交 E 的前一个提交开始 rebase。之所以从前一次开始,是为了包含提交 E。
git rebase -i 5961957~1
终端如下图所示:
4、修改有问题的提交
将提交 E 和 F 的状态都修改为 edit:
当前,停止在提交 E 处:
删除提交 E 中含有的 token:
将修改提交:
# 先添加到暂存区
git add .
# 再提交更改,并覆盖原来的提交
git commit --amend
控制台打印如下:
执行 git rebase --continue,继续 rebase。如下图所示,停止在了提交 F 上:
开始 rebase 提交 F
在提交 F 中删除 token:
将修改提交:
# 先添加到暂存区
git add .
# 再提交更改,并覆盖原来的提交
git commit --amend
控制台打印如下:
执行 git rebase --continue,继续 rebase。如下图所示,变基成功:
5、将最新的提交push到GitHub
成功!
参考:
- GitHub Docs:https://docs.github.com/en/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push