三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

23 — 安全实践:哪些历史能动,哪些绝对不能碰

23 — 安全实践:哪些历史能动,哪些绝对不能碰

写在前面:这一章要解决什么

你可能听说过这样一些「惨案」:有同学一条命令把公共分支历史改了,全组拉代码全报错;有人把数据库密码提交进仓库,服务器被清空。

你是不是也有这种感觉:想改点什么,又怕一改就出大事,干脆不敢动?

这一章就是帮你分清:哪些地方随便改,哪些地方绝对不能碰。学完你应该能:

  1. 说清「本地历史」和「已推送历史」的本质区别
  2. 知道amendrebasereset什么时候能用、什么时候绝不能用
  3. 碰到「共享分支上的坏提交」时,知道用revert而不是reset
  4. 永远不再把密钥、密码提交进仓库
  5. 万一真提交了敏感信息,知道第一步该干什么

读者设定:大一同学,会基本提交和分支,已开始在 GitHub/GitLab 上协作,但一听到「改历史」就紧张。


1. 定位:为什么「安全」要单开一章

1.1 一句话先记住

本地历史是你日记本上写的字,随时可以涂改;已推送历史是已经贴在公告栏上的通知,涂改会影响所有人。

这条线划不清,越往后协作越容易出事。

1.2 生活里的类比(先建立感觉)

  1. 日记本在你抽屉里,写错了可以涂掉重写——这是「本地历史」。
  2. 你已经把通知贴到班级公告栏上,全班同学都看到了——这是「已推送历史」。
  3. 你现在偷偷改公告栏,那些抄走旧通知的同学手里还是老版本,按老版本做事就会全部出错。

关键点:

  • 本地没推送的 → 你怎么折腾都行(最多自己倒霉)
  • 已经推送的 → 你的改动会影响所有人
  • main/master这种公共分支 → 即使只是「小改」,也可能害全组

1.3 和你已经会的对比

你已经会的容易误以为其实
git commit --amend改提交说明任何时候都能改只在没推送时安全
git reset退版本退回去就行了如果已推送,别人那里就乱了
git push --force强推解决冲突的快捷方式这是在公告栏上涂改通知
删掉文件重新提交就好了密码就消失了旧提交还在历史里,任何人都能翻到

认知锚点:Git 的历史一旦被别人拿到(推送过),就不属于你一个人了。

1.4 本章内容目标(学完能做什么)

目标你能做到的事
画清红线说清哪些操作在本地随便用,哪些在共享历史上绝对禁止
选对撤销碰到「共享分支上的坏提交」用revert,不用reset
保护密钥不会再把.env、密钥文件提交进仓库
应急处理万一提交了密钥,第一步先轮换凭据,再清历史

2. 本质:哪些历史能动,哪些绝对不能碰

2.1 先看总图

图:本地历史像日记本,涂改只影响自己;reflog是你的「日记修订记录」,即使涂改了也能翻出来。

图:已推送到远程的历史就像贴在公告栏上的通知,其他人已经基于它工作。

2.2 两大类历史:用白话拆开

(1)本地历史 = 你日记本上写的字
  • 这些提交只存在于你的电脑上,还没推送(push)过
  • 你怎么涂改、撕页、重写,都只影响你自己
  • 改坏了?reflog帮你找回之前的状态
  • 常见安全操作:commit --amendrebasereset
(2)已推送历史 = 已经贴在公告栏上的通知
  • 这些提交已经推送到远程仓库,队友可能已经pull过了
  • 你改掉其中一笔提交,队友下次拉代码就会冲突或丢东西
  • 绝对不要用resetrebase改写已推送历史
  • 如果必须撤销,用revert(创建一笔「反向提交」,不改历史)

2.3 什么算「共享」历史?

情况算不算共享原因
你的功能分支,从没推送过不算只有你一个人有
你的功能分支,推送了但只有你用灰色地带理论上别人可以拉,但实际只有你
main/master分支绝对共享全组人都在用
课程作业仓库的main共享老师和助教也在看

简单的判断方法:git log里能看到origin/开头的远程分支,就说明这份历史已经推送了。

2.4 受保护分支:main/master的特殊地位

  • main(或master)是项目的「主干」,所有功能最终都合并到这里
  • 永远不要对main执行force-push
  • 永远不要对main执行rebase
  • 需要「撤销」时,在main上只能用revert或走合并请求

2.5 金色法则(背下来)

法则白话解释
本地随便改日记本上写字,擦了重写没人管
已推送不要改公告栏上的通知不能涂改
拿不准就不改不确定有没有人拉走,就不动
实在要撤就revert再贴一张「作废通知」,不要涂改原通知
main永远不 force-push这是全组人的生命线
密钥永远不提交进了历史就跟泼出去的水一样

2.6 新手最常踩的坑

现象怎么办
force-push 后队友拉代码疯狂冲突避免;万一做了,让队友重新克隆
main做 rebase在共享分支上 rebase = 改写公共历史,禁止
reset撤已推送的坏提交revert代替
提交了.env文件先配.gitignore,已提交的话见应急处理

3. 建议的学习顺序

先建立「日记本 vs 公告栏」的直觉 → 画清红线:本地随便改,已推送不要动 → 学安全 amend:只改没推送的最近提交 → 学安全 rebase:只在你的功能分支上 → 学 revert:共享分支上唯一安全的「撤销」 → 密钥安全:.gitignore + 钩子 + 应急处理 → 完成章末小实验

不要急着用--force。这一章只要求知道边界在哪,碰到边界就停下来


4. 动手准备(可丢弃目录)

请找一个可以随便删的练习目录,不要用正在交的作业仓库练手。

mkdirlab-safetycdlab-safetygitinit-bmaingitconfig user.name"Ada Example"gitconfig user.email"ada@example.com"git--version# 本文用 Git 2.43.0 验证

先做几笔初始提交,后面实验要用:

printf'项目初始化\n'>README.mdgitaddREADME.mdgitcommit-m"docs: 初始化项目说明"printf'功能 A 的代码\n'>feature-a.txtgitaddfeature-a.txtgitcommit-m"feat: 添加功能 A"printf'功能 B 的代码\n'>feature-b.txtgitaddfeature-b.txtgitcommit-m"feat: 添加功能 B"

5. 跟着做:安全改写历史

5.1 安全修改最近一次提交:commit --amend(只在没推送时)

场景:你刚提交完,发现提交说明写了个错别字,或者忘了加一个文件。

gitlog--oneline-3# a2c3d4e feat: 添加功能 B# f1e2d3c feat: 添加功能 A# b0c1d2a docs: 初始化项目说明gitcommit--amend-m"feat: 添加功能 B(含边界检查)"gitlog--oneline-3# 9k8j7i6 feat: 添加功能 B(含边界检查)# f1e2d3c feat: 添加功能 A

白话翻译:

  • 最近一笔提交的说明被替换了
  • 提交哈希从a2c3d4e变成9k8j7i6,因为提交说明也是提交内容的一部分,改了说明等于创建新提交
  • 这只在本机安全!如果原来的提交已推送,改完再推就必须 force-push
条件能不能用amend
提交没推送,只在本机能,随便用
提交已推送到远程不能,除非你 100% 确定只有你一个人用这个分支
提交在main即使没推送也要谨慎,养成不用的习惯

5.2 安全变基:rebase(只在你的功能分支上)

场景:你的功能分支落后于main,想把main的最新内容合进来,但不想产生合并提交。

gitcheckout-bmy-featureprintf'我的新功能\n'>my-feature.txtgitaddmy-feature.txtgitcommit-m"feat: 我的新功能"gitrebase main

白话翻译:

  • rebase把你的提交「搬到」了main最新位置后面,原哈希全变
  • 这只在你的功能分支安全!如果这个分支还有人在用,他们历史就全乱了
条件能不能用rebase
只有你一个人在用的功能分支
已经推送到远程的共享分支绝对不能
main/master绝对不能

5.3 共享分支上唯一的「撤销」:revert

场景:已经推送到main的某笔提交有问题,你想「撤销」它,但不能改写历史。

gitlog--oneline-4# 9k8j7i6 feat: 添加功能 B(含边界检查)# f1e2d3c feat: 添加功能 Agitrevert f1e2d3c# 生成:x5y6z7a Revert "feat: 添加功能 A"

白话翻译:

  • revert不是「删掉那笔提交」,而是「再贴一张通知说前面那张作废」
  • 原提交还在历史里,但它的效果被新的「反向提交」抵消了,别人拉代码不会冲突
命令改不改历史适不适合共享分支
reset改(删掉提交)不适合
rebase改(重写提交)不适合
revert不改(新增反向提交)适合

5.4 万一要 force-push 的时候:--force-with-lease

有些场景确实需要强推(比如自己分支 rebase 后),但--force会直接覆盖别人新提交。

gitpush--force# 危险:不管别人有没有新提交,直接覆盖gitpush --force-with-lease# 安全:远程有你不了解的新提交,会拒绝推送
条件能不能用force-with-lease
你自己的功能分支,rebase 后需要推送
main/master不能
共享分支不能

5.5 看看你的安全网:reflog

即使你改了历史、做了reset,Git 的reflog还记录着之前的每一次操作。

gitreflog-5# a2c3d4e HEAD@{0}: commit: feat: 添加功能 B# f1e2d3c HEAD@{1}: commit: feat: 添加功能 A
  • reflog是「操作日记」,记录每次 HEAD 变化,默认保留 90 天
  • 丢了提交可用git reset --hard HEAD@{编号}找回,但先防患于未然

6. 命令按「用途」分组(先少后多)

6.1 不改历史的安全命令(放心用)

命令干什么
git status看当前状态
git log --oneline看提交历史
git reflog看操作记录(救命稻草)
git revert 哈希新增一笔「反向提交」,不改历史

6.2 只在本地安全的改写命令

命令安全前提
git commit --amend没推送
git rebase main只在只有你用的功能分支
git reset --soft HEAD~1没推送,改动留在暂存区
git reset --hard HEAD~1没推送,且确认改动不要了

6.3 远程推送相关

命令风险
git push
git push --force-with-lease中(只在自己的功能分支安全)
git push --force高(除非你 100% 确定后果)

6.4 凭据安全命令

命令 / 操作干什么
echo ".env" >> .gitignore.env文件排除在版本管理外
git rm --cached 文件把已跟踪的文件从仓库移除(但保留在工作区)
git log --all --full-history -- ".env"检查某文件是否曾出现在历史中

7. 对照表:降低记忆负担

7.1 本地 vs 共享:操作对照

操作本地(没推送)已推送(共享)
amend安全危险(等于 force-push)
rebase安全(只在自己的分支)禁止
reset安全禁止
revert可以但没必要唯一推荐

7.2resetvsrevert对照

resetrevert
改不改历史改(删提交)不改(加提交)
适合共享分支吗不适合适合
别人拉代码会冲突吗不会
像什么撕掉日记本的一页在公告栏贴张「作废通知」

7.3 安全决策流程

1. 这笔提交推送过吗? ├─ 没有 → 本地随便改(amend / rebase / reset 都行) └─ 有 → 继续判断 2. 在共享分支上吗? ├─ 是 → 用 revert └─ 否 → 继续判断 3. 只有你一个人在用这个分支吗? ├─ 是 → 可以 force-with-lease 推送 └─ 否 → 用 revert 4. 是 main/master 吗? ├─ 是 → 只能用 revert 或合并请求 └─ 否 → 参考上面判断

7.4 凭据安全对照

东西能不能提交怎么处理
.env文件不能写进.gitignore
数据库密码不能用环境变量
SSH 私钥不能写进.gitignore
API Token不能用环境变量
.env.example(不含真实密码)提交配置模板

8. 安全习惯(现在就能养成)

8.1 建议这样做

  • 每次提交前先git status,避免交错密钥文件
  • 推送前先git log看一遍要推什么
  • .gitignore在项目第一天就建好
  • 提交.env.example而不是.env
  • 功能分支开发,合并请求进main
  • 不确定能不能改,就不改;宁多一个 revert,也不 force-push
  • 密钥泄露先轮换,再清历史

8.2 暂时不要这样做

  • 不要git push --force
  • 不对main执行rebase
  • 不对main执行reset+ push
  • git add .然后闭眼提交
  • 不在公共分支上用amend
  • 不把密钥写在代码里

8.3 推荐的安全最小循环

gitcheckout-bmy-featuregitstatus# 重点!确认没有密钥文件gitadd具体文件gitstatusgitcommit-m"feat: 做了什么"gitpush-uorigin my-feature# 然后在 GitHub/GitLab 上开合并请求

9. 真实场景(没有项目经验也能懂)

9.1 课程大作业:你提交了数据库密码

你在config.py里写了:

DB_PASSWORD="my_super_secret_123"

然后git add .+git commit+git push一条龙。等同学提醒你密码不能提交时,你删掉重提——但旧提交里还是看得到密码。

正确做法(按顺序):

  1. 立刻轮换密码(第一优先级,比删历史重要)
  2. config.py加入.gitignore,或改用环境变量
  3. git rm --cached config.py,从仓库移除(文件仍在本地)
  4. 提交这次移除
  5. 如需彻底清理历史,用 BFG Repo Cleaner(进阶,建议找有经验的人帮忙)
  6. 通知所有组员重新克隆

9.2 发现main上有个严重 bug,想「撤掉」那笔提交

错误做法:

gitcheckout maingitreset--hardHEAD~1gitpush--force

正确做法:

gitcheckout maingitrevert 有问题的提交哈希gitpush

revert创建反向提交,效果等于撤销,但不改写历史,全组正常拉代码不会出问题。

9.3 你在自己分支上 rebase 后推送被拒

rebase 后本地哈希变了,正常git push会被拒。因为是自己的功能分支,可以用:

gitpush --force-with-lease

白话:「我知道远程版本跟我预期的不一样(因为我 rebase 了),但如果有人在我不知道的时候推了新东西,请拒绝我。」

9.4 不小心对main做了amend,已经推了

补救步骤:先reflog找 amend 前的哈希 →git reset --hard amend 之前的哈希git push --force(两害相权取其轻)→ 立刻通知组员重新克隆。更根本的办法:main上永远不做amend

9.5 GitHub/GitLab 的分支保护

常见的设置:推送main必须走合并请求、需要审批、禁止 force-push、必须过 CI。这些不是「烦人的限制」,而是安全网。管理员建议开启:禁止mainforce-push、要求合并请求、要求至少一人审批。

9.6 你想提交代码但怕改出问题

  • 在自己的功能分支上操作main不是你直接碰的地方
  • 功能分支随便试,改坏了reset就行
  • 准备好了开合并请求让同学看一眼;合并前main不会变

10. 稍微多懂一点点(可选)

10.1.gitignore最佳实践

项目第一天就建好.gitignore

.env .env.local *.pyc build/ dist/ .vscode/ .DS_Store *.pem *.key id_rsa*

更好的做法:用 gitignore.io 按项目类型生成模板。

10.2 预提交钩子:最后一道防线

用 pre-commit 框架配置检查规则:

repos:-repo:https://github.com/Yelp/detect-secretsrev:v1.4.0hooks:-id:detect-secrets

每次提交前自动扫描,发现疑似密钥就拒绝。

10.3 密钥检测工具

工具干什么
git-secrets扫描提交内容,发现密钥就拦截
detect-secrets检测代码中疑似密钥的字符串
gitleaks扫描仓库历史和当前代码中的凭据

建议至少用其中一个,作为预提交钩子。

10.4 万一真把密钥推上去了怎么办

千万不要反过来:先立刻轮换密钥(数据库密码→改密码;API Token→重新生成;SSH 密钥→删旧建新),再移除密钥改用环境变量、加.gitignoregit rm --cached 密钥文件、提交推送;必要时用git filter-repo清历史,最后通知协作者重新克隆。

为什么先轮换再清历史?从泄漏到发现可能已过很久,公开仓库可能早被爬虫扫到。清历史不如先换密钥。

10.5 签名提交与标签

gitcommit-S-m"feat: 添加重要功能"# 签名提交gittag-sv1.0-m"版本 1.0"# 签名标签gittag-vv1.0# 验证签名

签名提交像在日记上按手印,标签像盖章,保证来源可信。大一先知道有这机制即可。

10.6 分支保护规则配置

GitHub:Settings → Branches → Add branch protection rule,勾选 “Require a pull request before merging” 和 “Do not allow force pushes”。

GitLab:Settings → Repository → Protected branches,选择分支,设置合并/推送权限,开启 “No force push”。


11. 小实验(请一定动手)

实验甲:安全 amend。做一笔提交,用--amend改提交说明,确认哈希已变。通过标准:能说清 amend 前后提交哈希为什么不同。

实验乙:revert vs reset。三笔提交 A → B → C,用git reset --soft HEAD~1退掉 C;再提交 C 回来,用git revert撤销 B。通过标准:能用「日记本」和「公告栏」解释两种方式的区别。

实验丙:reflog 救命。git reset --hard HEAD~2故意丢掉两笔提交,再用git reflog找哈希恢复。通过标准:明白 reflog 是最后的救命稻草,但不要依赖它。

实验丁:.gitignore 防护。创建.env,加入.gitignore,确认不被跟踪;故意先git add .env,再用git rm --cached .env移除。通过标准:会在「忘了 ignore」和「已经 add 了」两种失误中正确补救。


12. 常见问题

问 1:对 main force-push 后全组都拉不了代码,怎么办?
立刻通知所有人,最稳的是让所有人重新克隆。然后立刻开启分支保护。

问 2:revert 之后想撤回 revert 怎么办?
再 revert 那笔 revert 提交就行了,等于恢复原来的效果。

问 3:自己分支 rebase 后拉代码总是冲突,正常吗?
不正常。若已推送又 force-push,队友会冲突。要么功能分支只自己用,要么不用 rebase 用 merge。

问 4:密钥已提交但还没推送,安全吗?
本机安全,但建议立刻从历史移除(git resetgit rebase -i)。养成提交前git status的习惯。

问 5:.gitignore 加了.env还是被跟踪?
因为.env之前已被git add过,.gitignore只对未跟踪文件生效。用git rm --cached .env解决。

问 6:--force-with-lease--force有什么区别?
--force一律覆盖;--force-with-lease会检查远程是否还是你以为的状态,有别人新提交就拒绝。更安全,但仍只能在自己分支用。

问 7:实在拿不准该用哪个命令怎么办?
git status/git log看清状态,问自己「推送过吗?在共享分支上吗?」。推送过或不确定,就用revert


13. 总结、学习路线与思维升华

13.1 这一章请记住的

记住什么
金色法则本地随便改,已推送不要动
核心区别日记本 vs 公告栏
共享分支撤销revert,不用reset
本地补救amendrebasereset只在没推送时安全
密钥安全第一天建.gitignore,提交前status检查
泄露应急先轮换密钥,再清历史
强推替代--force-with-lease--force安全

13.2 思维升华

按回车前先问自己:这笔提交推送过吗?有人在用吗?
如果不确定,就当它已经推送了。宁可多一个 revert,不要一次 force-push。
密钥进了历史就跟泼出去的水一样——防比治重要一万倍。

13.3 参考资料

  • Pro Git 中文版 — 重写历史
  • Pro Git 中文版 — 签署工作
  • git revert 说明 / git reflog 说明 / gitignore 说明
  • GitHub 分支保护规则
  • git-secrets / gitleaks / pre-commit

命令输出样例验证环境:Git 2.43.0;演示作者信息为虚构。

13.4 本章检查清单

  • 能用「日记本 vs 公告栏」说清本地和共享历史的区别
  • 知道amendrebasereset只在没推送时安全
  • 碰到共享分支上的坏提交,会选择revert而不是reset
  • 知道--force-with-lease--force安全在哪
  • 知道main永远不应该 force-push
  • 会在项目第一天建.gitignore排除密钥文件
  • 知道泄露密钥后第一步是轮换凭据,而不是删历史
  • 拿不准的时候,会选择「不改历史」的方案

学会 Git 的安全边界,不是为了束缚你,而是为了让你敢放心操作。知道红线在哪的人,比「什么都不怕」的人更安全。下一章我们继续深入实战场景。

← 返回列表