news 2026/8/22 14:19:09

Git撤销提交全攻略:从reset、revert到团队协作规范

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Git撤销提交全攻略:从reset、revert到团队协作规范

1. 项目概述:当提交记录成为“历史包袱”

在团队协作开发或者个人项目迭代中,使用 Git 进行版本控制几乎是现代开发者的标配。我们常常会使用fork操作来复制一个远程仓库,然后在自己的副本上进行功能开发或问题修复。在这个过程中,commit提交记录就像我们写下的开发日记,记录着每一次代码的变更。然而,这篇“日记”并非总是完美无缺。你可能遇到过这些情况:不小心提交了包含敏感信息的文件(比如配置文件里的数据库密码);或者一次提交引入了严重的 Bug,需要紧急回退;又或者只是觉得刚才的提交信息写得不够清晰,想重新整理一下提交历史。这时,“如何撤销已经提交的记录”就从一个简单的操作问题,变成了一个关乎代码安全、项目整洁度和团队协作效率的核心技能。

很多开发者,尤其是刚接触 Git 不久的朋友,在面对“撤销提交”这个需求时,第一反应可能是去网上搜索“git 如何撤销 commit”。搜索结果会给你一堆命令,比如git resetgit revertgit cherry-pick等等。看着这些命令和后面跟着的--soft--mixed--hard等参数,很容易就晕了。更让人头疼的是,如果你已经将本地的提交推送(push)到了远程仓库(比如你 fork 的那个源仓库,或者团队共用的仓库),情况会变得更加复杂,因为这会影响到其他人的工作。网络上热词里提到的 “fork operation failed”、“cannot retrieve latest commit at this time.” 以及 “commit and push checks failed” 等错误,很多时候就是在处理提交历史,特别是涉及远程仓库同步时操作不当引发的。

所以,这篇文章的目的不是简单地罗列命令,而是带你彻底理解“撤销提交”这个操作背后的不同场景、不同意图,以及对应的最佳实践。我们会从最基础的本地仓库操作讲起,一直深入到涉及远程协作时的处理策略。无论你是用命令行、VS Code、IntelliJ IDEA、Android Studio 还是 TortoiseGit 这样的 GUI 工具,理解了核心原理,你都能从容应对。我们会重点解析git reset的三种模式(soft, mixed, hard)到底改变了什么,git revert为何被称为“安全撤销”,以及在团队环境中应该如何选择。同时,我也会分享一些我踩过的坑和总结出的实操心得,比如为什么在reset --hard之后不要急着push,以及如何利用reflog这根“救命稻草”找回误删的提交。

2. 核心概念与场景辨析:撤销的“意图”决定“手段”

在动手敲任何命令之前,我们必须先搞清楚一个根本问题:你所谓的“撤销”到底是想达到什么效果?不同的意图,对应着完全不同的操作命令和风险等级。混淆它们,是导致后续一系列“疑难杂症”的根源。我们可以把常见的撤销意图分为以下几类,这比死记命令要重要得多。

2.1 场景一:仅修改上次提交的元信息(--amend)

这是最轻微的一种“撤销”。你的代码改动完全没有问题,已经通过git add暂存并git commit提交了。但事后发现,提交信息(commit message)写错了,比如有错别字,或者描述不清。又或者,你提交后才想起来还有一个小的文件修改忘记add进去了,你想把这次修改合并到上一次提交中,而不是创建一个新的提交。

对应命令与原理git commit --amend这个命令不会产生一个新的提交节点,而是修改最新的那个提交节点。你可以把它想象成“修订”最后一次提交。

  • 如果只想修改提交信息:直接运行git commit --amend,会弹出编辑器让你修改信息。
  • 如果想追加新的文件改动:先执行git add <file>把漏掉的文件暂存,然后运行git commit --amend。Git 会将暂存区的新改动和上次提交的改动合并,并允许你编辑提交信息。

注意--amend只能修改最近一次提交。并且,如果这个提交已经被推送到远程仓库,强制推送 (git push --force) 修改后的提交会重写历史,可能给协作者带来麻烦。在个人分支或尚未推送时使用是安全的。

2.2 场景二:彻底丢弃最近的提交(Reset)

这个意图比较“强硬”。你觉得最近的一次或几次提交完全是错误的,或者是一次失败的实验,你希望代码库的状态完全回退到这些提交之前的样子,就像它们从未发生过一样。根据你想保留工作目录和暂存区内容的不同,又分为三种子场景。

对应命令家族git reset [<mode>] [<commit>]这里的<commit>是你想要回退到的目标提交的哈希值(或相对引用,如HEAD~1)。而<mode>决定了“回退”的力度,这是理解reset的关键:

  • --soft:最温柔的模式。它只移动HEAD指针和当前分支指针到目标提交,但不触碰暂存区和工作目录。这意味着,你所有“错误提交”中的代码改动,都完好无损地保留在暂存区里,等待你重新审查、修改并提交。这适用于你想重新组织一次提交,或者将多次提交合并为一次。
  • --mixed默认模式。如果你只写git reset HEAD~1,Git 执行的就是--mixed。它移动HEAD和分支指针,并且重置暂存区,使其与目标提交保持一致。但是,工作目录的文件内容保持不变。结果就是,你撤销的提交中的代码改动,从“已提交状态”变成了“已修改但未暂存”的状态。你可以看到所有文件的改动(git status会显示为红色),然后决定是丢弃它们(git checkout -- <file>)还是重新挑选部分内容提交。
  • --hard:最彻底也最危险的模式。它移动HEAD和分支指针,并且同时重置暂存区和工作目录。你的代码会完全回到目标提交时的状态,所有之后的改动(包括未提交的)都将被永久丢弃(除非使用git reflog找回)。网络热词中提到的“点击 reset head, 将 reset type 选择 hard(重点)”指的就是这个操作,操作者必须非常清楚自己在做什么。

2.3 场景三:创建新的提交来抵消旧提交(Revert)

这是团队协作中最推荐、最安全的“撤销”方式。你的意图不是抹去历史,而是承认历史中有一个错误的提交,然后通过创建一个新的、内容相反的提交来“抵消”它产生的影响。这就像财务记账中的“红字冲销”。这样做的好处是,提交历史被完整保留,任何人都能清晰地看到:某时某刻引入了一个 Bug(提交 A),然后在另一时刻修复了它(提交 B,即 revert 提交)。这对于需要追溯历史的项目至关重要。

对应命令git revert <commit>这个命令会分析指定提交(<commit>)引入了哪些改动,然后尝试生成一个反向的补丁,并创建一个新的提交来应用这个反向补丁。如果遇到冲突,Git 会停下来让你手动解决。

reset的核心区别

  • reset是“回到过去”,并选择性遗忘之后的历史。
  • revert是“留在现在”,但新增一个提交来纠正过去的错误。

2.4 场景四:复杂历史整理(交互式变基与 Cherry-pick)

当你的撤销需求不仅仅是针对最近几次提交,或者你想从其他分支“摘取”特定的提交时,就需要更高级的工具。

  • git cherry-pick <commit>:这个命令不是严格意义上的“撤销”,但它常被用于修复性操作。例如,你在主分支main上发现一个 Bug,这个 Bug 是在某个特定的旧提交中引入的。你可以在修复分支上使用revert来撤销它,但有时你可能只想把这个“修复动作”(即撤销某个特定提交的改动)应用到其他分支(比如某个长期维护的发布分支)。这时,你可以先在主分支上执行git revert <bad-commit>生成一个修复提交,然后用cherry-pick将这个修复提交“摘”到其他分支上。热词中 “tortoisegit如何把分支a的指定提交记录推送到分支b上” 的一部分解决方案就涉及cherry-pick
  • git rebase -i(交互式变基):这是整理提交历史的“瑞士军刀”。你可以用它来合并多个提交、修改提交信息、删除提交、调整提交顺序等。本质上,它也是通过“重新应用”提交来重写历史,因此同样存在远程推送时需要强制推送的问题。对于“撤销”场景,你可以在交互式列表中将某个提交前的pick改为drop,从而在变基过程中丢弃它。

3. 命令行实战:从本地到远程的完整撤销流程

理解了意图和命令,我们进入实战环节。我会按照从简单到复杂,从本地到远程的顺序,用具体的命令行示例带你走一遍完整的流程。请务必在一个测试仓库中跟着操作,感受每一步带来的变化。

3.1 准备工作:创建一个模拟仓库

首先,我们创建一个临时目录和 Git 仓库来模拟整个场景。

mkdir git-undo-demo && cd git-undo-demo git init echo "初始内容" > file1.txt git add file1.txt git commit -m "初始提交:添加 file1.txt"

现在,我们有了一个干净的起点,提交历史只有一次。

3.2 模拟“错误”提交

我们接着做一些“错误”的提交。

# 模拟一次好的提交 echo "功能A开发" >> file1.txt git add file1.txt git commit -m "feat: 开发功能A" # 模拟一次有问题的提交(我们想撤销这个) echo "这是一个错误的修改,包含敏感信息:password=123456" >> file1.txt git add file1.txt git commit -m "fix: 临时修复某个问题" # 再模拟一次好的提交,让历史更复杂 echo "功能B开发" >> file1.txt git add file1.txt git commit -m "feat: 开发功能B"

现在,使用git log --oneline查看历史,应该看到类似下面的输出(哈希值会不同):

d3b7a5f (HEAD -> main) feat: 开发功能B c2e9f1b fix: 临时修复某个问题 a1b2c3d feat: 开发功能A 8765432 初始提交:添加 file1.txt

我们的目标是处理那个“fix: 临时修复某个问题”的提交(c2e9f1b),它引入了敏感信息。

3.3 场景实战:使用git reset撤销本地提交

假设这个错误提交还没有推送到远程,我们想彻底丢弃它。

情况A:使用--mixed(默认)我们想撤销最后一次提交(feat: 开发功能B),但保留它的改动在工作目录。

git reset HEAD~1 # 等价于 git reset --mixed HEAD~1

运行后,git log --oneline显示HEAD指向了c2e9f1b(那个错误提交),feat: 开发功能B的提交从历史中消失了。运行git status,你会看到file1.txt被修改了(红色),修改内容正是“功能B开发”那一行。现在你可以决定是丢弃这个修改(git checkout -- file1.txt)还是重新提交。

情况B:使用--hard彻底丢弃我们想直接回到“功能A开发”之后的状态,丢弃之后的所有改动(包括错误提交和功能B)。

git reset --hard a1b2c3d # 将 a1b2c3d 替换为你的 “feat: 开发功能A” 提交的哈希值

警告:这个操作会永久丢弃工作目录和暂存区中所有未提交的、以及a1b2c3d之后提交的改动。执行后,git log只会显示到a1b2c3dfile1.txt的内容也回到了只有“初始内容”和“功能A开发”两行的状态。请确保你真的不需要这些改动

实操心得:在执行git reset --hard之前,一个非常好的习惯是使用git diff HEADgit stash来确认或临时保存你可能会丢失的改动。一旦执行,只有git reflog可能救你。

3.4 场景实战:使用git revert安全撤销

现在考虑更常见的团队场景:那个错误的提交(c2e9f1b)已经被推送到远程共享仓库了。我们不能使用reset重写历史,而应该使用revert

首先,我们需要回到包含错误提交的历史状态。如果你刚才做了reset --hard,可以用git reflog找到feat: 开发功能B的提交哈希,然后git reset --hard <那个哈希>回去。或者直接重新模拟一遍提交历史。

假设我们现在处于feat: 开发功能B提交之后的历史。我们想要撤销中间的fix: 临时修复某个问题提交,但保留两头的功能提交。

git revert c2e9f1b # 将 c2e9f1b 替换为你的错误提交哈希

Git 会尝试自动创建一个新的提交,来抵消c2e9f1b的改动。因为它是在file1.txt末尾添加了一行,所以revert会尝试删除那一行。如果那一行前后没有其他冲突的修改,Git 会自动成功并打开编辑器让你填写新的提交信息。默认信息是 “Revert “fix: 临时修复某个问题””,你可以修改后保存退出。

完成后,运行git log --oneline,你会看到类似:

e4f5g6h (HEAD -> main) Revert "fix: 临时修复某个问题" d3b7a5f feat: 开发功能B c2e9f1b fix: 临时修复某个问题 a1b2c3d feat: 开发功能A 8765432 初始提交

历史被完整保留了,但多了一个revert提交。查看file1.txt的内容,包含敏感信息的那一行已经被移除,但“功能B开发”的内容还在。现在你可以安全地将这个revert提交push到远程,通知团队成员这个错误已被修复。

3.5 处理冲突:revertreset中的拦路虎

现实很少一帆风顺。当你执行revert一个较旧的提交,或者执行reset后重新修改提交时,很可能遇到冲突。

git revert冲突:如果 Git 无法自动应用反向补丁(比如要删除的行已经被后续提交修改了),它会停下来,标记冲突文件。你需要:

  1. 手动打开冲突文件,解决冲突(删除那些不应该存在的内容)。
  2. 使用git add <file>标记冲突已解决。
  3. 运行git revert --continue来完成revert操作。如果想放弃这次revert,运行git revert --abort

git reset后的冲突:这通常发生在你reset --mixed之后,修改了代码,然后想合并成一个新提交,但修改的内容与当前版本有冲突。实际上,这不算 Git 命令的冲突,而是你后续合并时(比如git mergegit rebase)可能遇到的。核心是,reset本身不产生冲突,它只是改变了分支的指向。

3.6 终极后悔药:git reflog

如果你误操作了git reset --hard,把还想保留的提交弄丢了怎么办?别慌,只要操作是最近发生的,git reflog很可能能救你。reflog记录了本地仓库中HEAD和分支引用所有的移动记录。

git reflog

你会看到一个列表,显示HEAD的移动历史,包括每次移动的哈希值、操作类型和提交信息。

e4f5g6h (HEAD -> main) HEAD@{0}: revert: Revert "fix: 临时修复某个问题" d3b7a5f HEAD@{1}: reset: moving to d3b7a5f a1b2c3d HEAD@{2}: reset: moving to a1b2c3d ...

找到你丢失的提交对应的记录(比如d3b7a5f那次feat: 开发功能B的提交),然后使用git reset --hard d3b7a5f就可以让分支回到那个状态。注意reflog是本地记录,会过期清理(默认90天),且不会推送到远程。所以这是一剂“本地”的后悔药。

4. 图形化工具 (GUI) 操作指南

对于习惯使用图形界面的开发者,理解上述原理同样重要,因为所有 GUI 工具都是对这些底层命令的封装。这里以几个热门工具为例,说明如何进行操作。

4.1 VS Code 源代码管理

VS Code 内置的 Git 功能非常强大。

  1. 查看历史与撤销最新提交:打开源代码管理面板,点击分支名旁边的“...”更多按钮,选择“提交”下的“撤销上次提交”。这相当于执行了git reset HEAD~1--mixed模式)。撤销的改动会出现在“更改”区域。
  2. 使用revert:在“提交”历史记录中(源代码管理面板顶部的“...” -> “提交” -> “显示提交历史”),找到你想撤销的提交,右键点击,选择“还原提交”。VS Code 会自动执行git revert并让你填写提交信息。
  3. 注意:VS Code 没有直接提供reset --hard的图形按钮,因为这很危险。你需要通过终端执行命令。

4.2 IntelliJ IDEA / Android Studio

JetBrains 系列的 IDE 对 Git 的支持非常全面。

  1. 撤销提交:在“提交”工具窗口(Alt+0)或“版本控制”日志中,选中你想要撤销的提交。右键菜单中:
    • Reset Current Branch to Here...:这就是git reset。点击后会弹窗让你选择模式:SoftMixedHard,对应命令行的三种模式。你可以清晰地看到每个模式的描述。
    • Revert Commit:这就是git revert,会创建一个新的反转提交。
  2. Drop Commit:在日志中右键提交,还有一个“Drop Commit”选项。这个操作相当于在交互式变基 (rebase -i) 中drop这个提交。这是一个重写历史的操作,仅适用于本地未推送的提交。热词中 “android studio drop commit 怎么找回” 的答案就是使用git reflog
  3. 解决冲突:无论是revert还是reset后合并,IDEA 都会在出现冲突时提供强大的三窗格合并工具,让你可视化地解决冲突。

4.3 Fork / SourceTree 等 Git 客户端

以 Fork 客户端为例(这也是你标题中提到的工具):

  1. 重置 (Reset):在提交历史图中,选中你想要回退到的目标提交。右键菜单选择“重置 master 分支到此次提交...”(“master”是你的分支名)。弹出的对话框会让你选择重置类型:SoftMixedHard,效果与命令行一致。这就是热词中描述的流程。
  2. 反转 (Revert):在提交历史图中,右键点击某个提交,选择“反转提交...”。客户端会帮你执行git revert
  3. 交互式变基:通常可以在分支菜单或历史图右键菜单中找到“交互式变基”,允许你以图形化方式重新排序、压缩、编辑提交历史。

GUI 工具通用建议:虽然 GUI 工具方便,但在执行reset --harddrop commit这类危险操作前,弹出的确认对话框一定要仔细阅读描述。理解它对应的是哪个 Git 命令,会造成什么后果。

5. 涉及远程仓库的协作规范与风险管控

当你的操作涉及已经推送到远程仓库(如 GitHub, GitLab)的提交时,就需要格外小心,因为你的操作会影响其他拉取了这些代码的协作者。

5.1 黄金法则:对公共分支只使用git revert

对于团队共享的主分支(如maindevelop),一旦提交被推送,就应视为“已发布”。绝对不要使用git resetgit commit --amendgit rebase来修改这些分支的历史。因为这些操作会改变提交的哈希值,导致本地历史与远程历史不一致。当你强行推送 (git push --force) 时,会覆盖远程历史。其他协作者在此之后执行git pull时会遇到复杂的合并冲突,历史记录也会变得混乱不堪。

正确的做法永远是使用git revert。它添加新的历史,而不是修改旧历史,因此不会与远程历史冲突,可以安全地push

5.2 个人特性分支的强制推送

如果你是在自己的特性分支(例如feature/xxx)上工作,并且确定这个分支只有你一人在使用,那么有时重写历史是可以接受的。例如,你在本地进行了多次琐碎的提交(“fix typo”, “oops”, “really fix”),想合并成一个清晰的提交后再推送给团队审查。

流程通常是:

  1. 在本地使用git rebase -i或多次git reset --soft来整理提交历史。
  2. 使用git push --force-with-lease强制推送到远程特性分支。
    • --force-with-lease--force更安全,它会检查远程分支在你上次拉取后是否有其他人推送了新的提交。如果有,它会拒绝强制推送,防止你无意中覆盖他人的工作。

5.3 处理 “Cannot retrieve latest commit at this time” 等远程错误

网络热词中提到了 “github cannot retrieve latest commit at this time.” 这个错误。这通常不是由你的撤销操作直接引起的,而是网络问题或 GitHub 服务暂时不可用导致的。当你执行git pushgit fetch时,客户端无法从远程仓库获取最新信息。

解决方法

  1. 检查网络:确保你的网络连接正常。
  2. 重试:等待片刻后重试操作。
  3. 验证远程地址:运行git remote -v检查远程仓库地址是否正确。
  4. 使用 SSH 而非 HTTPS:有时 HTTPS 代理或证书问题会导致连接失败,尝试使用 SSH 协议克隆和推送可能更稳定。

如果这个错误发生在你强制推送之后,并且其他协作者已经开始拉取代码,那么问题可能更复杂,可能需要团队协调,让所有人基于新的远程历史重新调整自己的本地分支(这很麻烦,所以再次强调公共分支不要强制推送)。

6. 高级技巧与最佳实践

掌握了基本操作后,一些进阶技巧和习惯能让你的版本控制更加得心应手。

6.1 组合拳:reset --soft+ 重新提交

这是一个非常实用的工作流,用于优化最后一次提交。

  1. 你完成了一次提交,但马上发现漏了一个文件,或者提交信息需要大改。
  2. 使用git reset --soft HEAD~1。这会让上次提交的改动全部回到暂存区。
  3. 添加漏掉的文件:git add missed-file.txt
  4. 重新提交:git commit -m “新的、更清晰的提交信息”。 这样,你就用一次干净的提交替换了之前不完美的提交,而没有留下“修正笔误”这样的冗余历史。

6.2 使用git stash暂存改动

在执行任何可能丢失工作目录改动的操作(如git reset --hard、切换分支)之前,如果你对当前的修改还不确定,可以先使用git stash将改动保存到一个临时区域。

git stash push -m “暂存当前工作,准备执行reset” git reset --hard <某个提交> # ... 执行一些操作 git stash pop # 恢复暂存的改动

git stash是你的安全网,尤其在进行一些破坏性实验时。

6.3 编写清晰的提交信息

很多“撤销”操作源于糟糕的提交。养成编写清晰、规范的提交信息的习惯,能从源头上减少撤销的需求。推荐使用类似 Conventional Commits 的规范:

  • feat:新功能
  • fix:修复 Bug
  • docs:文档更新
  • style:代码格式调整(不影响逻辑)
  • refactor:代码重构
  • test:测试相关
  • chore:构建过程或辅助工具变动

例如:fix(auth): 修复用户登录令牌过期时间计算错误。这样的历史一目了然,revert时也更容易定位目标。

6.4 利用分支进行隔离

不要在主分支上直接进行实验性的开发。为每个新功能或修复创建一个新的特性分支。

git checkout -b feature/awesome-new-feature # 在特性分支上尽情提交,甚至“瞎搞” # 如果搞砸了,想回到干净状态,简单粗暴: git checkout main git branch -D feature/awesome-new-feature # 删除特性分支 # 然后重新拉一个干净的分支开始

分支的成本极低,它们是进行风险隔离的最佳实践。

撤销 Git 提交记录,远不止是记住一两条命令。它是一场关于意图、场景和协作规范的思维训练。核心在于区分“重写历史”和“添加新历史”的边界。对于本地、未推送的草稿,你可以自由使用resetrebase来整理;对于已共享的提交,revert是唯一安全的选择。图形化工具让操作更直观,但理解其背后的命令原理,能让你在遇到复杂情况时依然胸有成竹。最后,好的习惯(清晰的提交、分支隔离、勤用stash)能让你最大限度地避免陷入需要“撤销”的境地。记住,reflog是你最后的保障,但在团队中,清晰的沟通和规范的操作,才是最高效的“撤销”手段——让问题尽量不发生。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 14:17:14

Peacock快速入门教程:5个步骤从零部署你的HITMAN暗杀世界私服

Peacock快速入门教程&#xff1a;5个步骤从零部署你的HITMAN暗杀世界私服 【免费下载链接】Peacock The Peacock Project is a HITMAN™ World of Assassination trilogy server replacement. 项目地址: https://gitcode.com/gh_mirrors/pe/Peacock Peacock 是一个免费的…

作者头像 李华
网站建设 2026/8/22 14:16:25

LLM-as-a-Verifier插件:为DeepSeek Harness工作流构建AI质检员

最近在折腾 DeepSeek Harness 这类 LLM 应用框架时&#xff0c;我遇到了一个几乎所有开发者都会头疼的问题&#xff1a;如何让 AI 生成的内容不只是“看起来对”&#xff0c;而是“真的能用”&#xff1f;你肯定也经历过。用框架跑一个任务&#xff0c;比如生成一段代码、写一份…

作者头像 李华
网站建设 2026/8/22 14:15:50

taskt:800+命令免费RPA自动化工具完整上手指南

taskt&#xff1a;800命令免费RPA自动化工具完整上手指南 【免费下载链接】taskt taskt (pronounced tasked and formely sharpRPA) is free and open-source robotic process automation (rpa) built in C# powered by the .NET Framework 项目地址: https://gitcode.com/gh…

作者头像 李华
网站建设 2026/8/22 14:13:34

LLaMA-Factory MoE模型微调实战:3 步跑通 30B-A3B,不再爆显存

LLaMA-Factory MoE模型微调实战&#xff1a;3 步跑通 30B-A3B&#xff0c;不再爆显存 【免费下载链接】LlamaFactory Unified Efficient Fine-Tuning of 100 LLMs & VLMs (ACL 2024) 项目地址: https://gitcode.com/GitHub_Trending/ll/LlamaFactory 做 LLaMA-Facto…

作者头像 李华
网站建设 2026/8/22 14:12:37

Dark Reader 暗黑模式教程:快速让网页夜间阅读不刺眼

Dark Reader 暗黑模式教程&#xff1a;快速让网页夜间阅读不刺眼 【免费下载链接】darkreader Dark Reader Chrome and Firefox extension 项目地址: https://gitcode.com/gh_mirrors/da/darkreader 如果你经常在深夜浏览网页&#xff0c;刺眼的白色背景确实很伤眼睛。D…

作者头像 李华