目录
[一、先把 Git 想成"游戏存档系统"](#一、先把 Git 想成“游戏存档系统”)
[二、Git 管理的是"快照",不是简单的修改记录](#二、Git 管理的是“快照”,不是简单的修改记录)
[三、Git 最重要的三个区域](#三、Git 最重要的三个区域)
[1. 工作区 Working Directory](#1. 工作区 Working Directory)
[2. 暂存区 Staging Area](#2. 暂存区 Staging Area)
[3. 本地仓库 Repository](#3. 本地仓库 Repository)
[第二步:执行 git add](#第二步:执行 git add)
[第三步:执行 git commit](#第三步:执行 git commit)
[六、commit 到底是什么?](#六、commit 到底是什么?)
[七、HEAD 是什么?](#七、HEAD 是什么?)
[十二、GitHub 和 Git 到底什么关系?](#十二、GitHub 和 Git 到底什么关系?)
[十三、push 的原理是什么?](#十三、push 的原理是什么?)
[十四、pull 的原理是什么?](#十四、pull 的原理是什么?)
[十五、clone 的原理是什么?](#十五、clone 的原理是什么?)
[十六、reset、restore、revert 的区别](#十六、reset、restore、revert 的区别)
[1. git restore](#1. git restore)
[2. git reset](#2. git reset)
[3. git revert](#3. git revert)
[十七、.git 目录是什么?](#十七、.git 目录是什么?)
[十八、Git 底层保存的四类对象](#十八、Git 底层保存的四类对象)
[1. blob](#1. blob)
[2. tree](#2. tree)
[3. commit](#3. commit)
[4. tag](#4. tag)
[十九、为什么 Git 中经常看到一串哈希?](#十九、为什么 Git 中经常看到一串哈希?)
[后端程序员 Git 日常高频操作:按"目标"整理](#后端程序员 Git 日常高频操作:按“目标”整理)
[1. 克隆远程仓库](#1. 克隆远程仓库)
[2. 进入项目](#2. 进入项目)
[3. 查看当前状态](#3. 查看当前状态)
[4. 查看所有分支](#4. 查看所有分支)
[5. 查看远程仓库地址](#5. 查看远程仓库地址)
[1. 切换到主开发分支](#1. 切换到主开发分支)
[2. 确认工作区是否干净](#2. 确认工作区是否干净)
[3. 拉取远程最新提交](#3. 拉取远程最新提交)
[1. 先进入基准分支](#1. 先进入基准分支)
[2. 同步最新代码](#2. 同步最新代码)
[3. 创建并切换新分支](#3. 创建并切换新分支)
[stash 的含义](#stash 的含义)
[查看 stash](#查看 stash)
[恢复并删除 stash](#恢复并删除 stash)
[恢复但保留 stash](#恢复但保留 stash)
[删除某个 stash](#删除某个 stash)
[删除所有 stash](#删除所有 stash)
[九、撤销已经执行的 git add](#九、撤销已经执行的 git add)
[reset 和 revert 的区别](#reset 和 revert 的区别)
[rebase 的安全原则](#rebase 的安全原则)
[十四、解决 merge 或 rebase 冲突](#十四、解决 merge 或 rebase 冲突)
[merge 冲突处理完成](#merge 冲突处理完成)
[rebase 冲突处理完成](#rebase 冲突处理完成)
[十五、rebase 后如何推送远程分支](#十五、rebase 后如何推送远程分支)
[二十、新建本地仓库并上传到 GitHub](#二十、新建本地仓库并上传到 GitHub)
[.gitignore 示例](#.gitignore 示例)
[已经被 Git 跟踪的文件,新增 .gitignore 不生效](#已经被 Git 跟踪的文件,新增 .gitignore 不生效)
[二十三、查看某个文件为什么没有被 Git 跟踪](#二十三、查看某个文件为什么没有被 Git 跟踪)
[二十四、误删分支或误 reset 后恢复代码](#二十四、误删分支或误 reset 后恢复代码)
[查看 HEAD 移动记录](#查看 HEAD 移动记录)
[连 .gitignore 忽略的文件也删除](#连 .gitignore 忽略的文件也删除)
[二十八、二分查找定位引入 Bug 的提交](#二十八、二分查找定位引入 Bug 的提交)
[1. 开始新功能](#1. 开始新功能)
[2. 开发过程中检查修改](#2. 开发过程中检查修改)
[3. 选择本次提交内容](#3. 选择本次提交内容)
[4. 检查待提交内容](#4. 检查待提交内容)
[5. 本地运行测试](#5. 本地运行测试)
[6. 创建提交](#6. 创建提交)
[7. 推送远程](#7. 推送远程)
[8. 开发期间同步 develop](#8. 开发期间同步 develop)
[9. 解决冲突后继续](#9. 解决冲突后继续)
[10. rebase 后更新远程分支](#10. rebase 后更新远程分支)
[11. PR 合并后清理](#11. PR 合并后清理)
[三十一、紧急线上 Bug 修复工作流](#三十一、紧急线上 Bug 修复工作流)
[1. 提交信息写错了,尚未 push](#1. 提交信息写错了,尚未 push)
[2. 漏提交一个文件,尚未 push](#2. 漏提交一个文件,尚未 push)
[3. git add 加多了](#3. git add 加多了)
[4. 文件改坏了,还没有 add](#4. 文件改坏了,还没有 add)
[5. 本地最后一次提交不想要了,但保留代码](#5. 本地最后一次提交不想要了,但保留代码)
[6. 已推送的提交需要撤销](#6. 已推送的提交需要撤销)
[7. 需要临时切换分支,代码未完成](#7. 需要临时切换分支,代码未完成)
[8. 拉取代码发生冲突](#8. 拉取代码发生冲突)
[9. 误 reset 或误删分支](#9. 误 reset 或误删分支)
[10. rebase 后 push 被拒绝](#10. rebase 后 push 被拒绝)
[三十三、建议配置的 Git 别名](#三十三、建议配置的 Git 别名)
Git 的本质可以先压缩成一句话:
Git 是一个"给项目目录拍快照,并用指针管理这些快照"的版本管理系统。
它不是简单地"备份文件",也不只是"把代码上传到 GitHub"。
一、先把 Git 想成"游戏存档系统"
你写代码的过程类似玩游戏:
写了一部分代码
↓
觉得当前状态没问题
↓
保存一个存档
↓
继续修改
↓
改坏了
↓
回到以前的存档
Git 中的"存档"就是:
git commit
每执行一次 commit,Git 就记录一次项目状态。
例如:
提交1:完成登录功能
提交2:完成图片上传功能
提交3:完成图片搜索功能
它们形成一条历史链:
A ← B ← C
含义是:
-
A:第一次提交 -
B:基于 A 修改得到 -
C:基于 B 修改得到 -
当前代码通常指向 C
二、Git 管理的是"快照",不是简单的修改记录
很多人会认为 Git 保存的是:
第一行增加了什么
第二行删除了什么
从使用角度看,它确实可以展示差异,但 Git 的核心模型更接近:
每次提交,都保存当时整个项目的目录结构和文件内容快照。
例如第一次提交时:
项目快照 A
├── UserController.java
├── UserService.java
└── application.yml
第二次提交时:
项目快照 B
├── UserController.java
├── UserService.java
├── FileController.java
└── application.yml
不过 Git 不会粗暴地把完全相同的文件重复保存很多份。
如果两个提交里的 UserService.java 内容完全一样,Git 会复用原来的对象。
所以 Git 可以理解成:
内容相同 → 复用
内容变化 → 保存新对象
提交对象 → 记录本次项目快照
三、Git 最重要的三个区域
这是理解 Git 的关键。
工作区 → 暂存区 → 本地仓库
1. 工作区 Working Directory
就是你现在肉眼看到的项目文件。
例如:
java
AIWear/
├── src/
├── pom.xml
└── README.md
你在 IDEA 中修改代码,修改的就是工作区。
例如你改了:
java
public String login() {
return "success";
}
此时 Git 知道文件变了,但还没有形成提交。
可以查看:
java
git status
2. 暂存区 Staging Area
执行:
git add .
就是把当前修改放入暂存区。
暂存区可以理解为:
准备进入下一次提交的文件清单和内容。
例如你修改了三个文件:
UserController.java
UserService.java
README.md
但这次只想提交登录相关代码,不想提交 README。
可以执行:
git add src/main/java/UserController.java
git add src/main/java/UserService.java
此时:
工作区:
UserController.java 已修改
UserService.java 已修改
README.md 已修改
暂存区:
UserController.java
UserService.java
下一次 commit 只会提交暂存区里的内容。
因此:
git add
不是"保存到 Git 历史",而是:
选择哪些内容进入下一次提交。
3. 本地仓库 Repository
执行:
git commit -m "完成登录功能"
Git 才会真正生成一个提交记录。
这个提交保存在本机项目的:
.git
目录中。
也就是说,即使没有网络,即使没有 GitHub,你照样可以:
git add
git commit
git log
git reset
git checkout
因为 Git 本身就是本地版本管理工具。
四、为什么需要暂存区?
很多人第一次学 Git 时会觉得:
为什么不能直接 commit?
为什么中间要多一个 add?
因为一次修改中,可能混杂了多件事情。
例如你今天同时做了:
1. 修复登录 Bug
2. 修改 README
3. 调试时加入大量 println
4. 顺手改了图片上传逻辑
但你希望提交历史清晰:
提交1:修复登录空指针问题
提交2:完善项目说明
提交3:优化图片上传异常处理
这时就可以使用暂存区分批提交。
例如:
git add UserService.java
git commit -m "修复登录空指针问题"
然后:
git add README.md
git commit -m "完善项目说明"
所以暂存区不是多余步骤,它承担的是:
组织提交边界。
五、一次完整操作到底发生了什么?
假设你修改了:
UserController.java
第一步:修改文件
工作区发生变化
此时:
git status
可能显示:
modified: UserController.java
状态可以理解为:
工作区版本 ≠ 暂存区版本
第二步:执行 git add
git add UserController.java
Git 会把该文件当前内容放进暂存区。
此时:
工作区版本 = 暂存区版本
暂存区版本 ≠ 上一次提交版本
第三步:执行 git commit
git commit -m "修改登录接口"
Git 根据暂存区内容生成一个新提交。
此时:
暂存区版本 = 最新提交版本
如果提交后没有再修改文件:
工作区 = 暂存区 = 最新提交
这时 git status 会显示:
nothing to commit, working tree clean
六、commit 到底是什么?
一个 Git 提交并不只是提交说明。
一个 commit 大致包含:
commit
├── 本次项目快照
├── 父提交是谁
├── 作者
├── 提交时间
└── 提交说明
例如:
Commit C
├── 项目快照:第三版代码
├── parent:Commit B
├── author:小卢
├── time:2026-07-10
└── message:完成图片检索功能
因为每个提交都记录父提交,所以提交形成链:
A ← B ← C ← D
其中 D 表示当前最新提交。
七、HEAD 是什么?
HEAD 是 Git 中一个非常重要的概念。
它可以理解为:
你当前所在的位置。
正常情况下:
HEAD → main → Commit D
意思是:
-
你当前在
main分支 -
main指向提交 D -
HEAD指向main
图示:
A ← B ← C ← D
↑
main
↑
HEAD
当你继续提交一次:
git commit -m "新增功能"
就会变成:
A ← B ← C ← D ← E
↑
main
↑
HEAD
本质上就是:
创建新提交 E
main 指针从 D 移动到 E
八、分支的原理是什么?
很多人把分支想得很复杂。
实际上,Git 分支本质上只是:
一个指向某个提交的可移动指针。
例如当前:
A ← B ← C
↑
main
创建分支:
git branch feature
结果:
A ← B ← C
↑
main
↑
feature
此时两个分支都指向 C。
切换到 feature:
git switch feature
然后提交两次:
A ← B ← C ← D ← E
↑ ↑
main feature
注意:
-
main仍然停在 C -
feature已经走到 E -
两个分支共享前面的 A、B、C
所以分支不是完整复制一份项目。
不是:
复制一个新文件夹
而是:
创建一个很轻量的提交指针
这也是 Git 创建分支非常快的原因。
九、切换分支为什么文件会变化?
假设:
main → C
feature → E
执行:
git switch main
Git 会做两件事:
1. HEAD 指向 main
2. 工作区恢复为提交 C 对应的项目快照
所以你会发现文件内容变回 main 分支的状态。
再执行:
git switch feature
工作区又会恢复成 E 的状态。
所以"切换分支"本质上是:
改变当前位置,并把工作区切换到对应提交的快照。
十、合并分支是什么原理?
假设:
A ← B ← C
├── D ← E feature
└── F main
feature 分支完成了功能,main 也有了自己的修改。
执行:
git switch main
git merge feature
Git 会尝试把两条开发线合并:
A ← B ← C
├── D ← E
└── F ← M
M 是合并提交:
M 的父提交有两个:
1. F
2. E
因此 merge 的本质是:
把两个分支的修改结果整合成一个新快照。
十一、冲突为什么会出现?
例如 main 分支将同一行改成:
return "main";
feature 分支将同一行改成:
return "feature";
Git 无法判断应该保留哪个,于是产生冲突:
<<<<<<< HEAD
return "main";
=======
return "feature";
>>>>>>> feature
这不是 Git 出错,而是:
Git 无法代替开发者判断业务上哪个修改正确。
你手动修改成最终结果:
return "success";
然后:
git add .
git commit
冲突就解决了。
十二、GitHub 和 Git 到底什么关系?
这个必须分清。
Git
是安装在本机上的版本控制工具。
负责:
记录版本
创建提交
创建分支
切换版本
合并代码
查看历史
GitHub
是互联网上的 Git 仓库托管平台。
负责:
远程保存代码
多人协作
代码审查
Pull Request
Issues
Actions
项目展示
关系类似:
Git = 本地版本管理工具
GitHub = 远程代码托管网站
没有 GitHub,Git 仍然能正常工作。
没有 Git,本地通常也无法完成规范的 GitHub 版本管理。
十三、push 的原理是什么?
本地有一条提交历史:
A ← B ← C
↑
main
GitHub 远程仓库可能只有:
A ← B
↑
origin/main
执行:
git push origin main
就是把本地缺少的提交 C 上传到远程。
上传后:
本地:
A ← B ← C
↑
main
远程:
A ← B ← C
↑
origin/main
这里:
-
origin:远程仓库的默认名称 -
main:分支名 -
origin/main:本地记录的远程 main 分支位置
所以 push 不是简单地"上传整个项目文件夹"。
它更准确的含义是:
将本地仓库中远程缺少的 Git 对象和提交同步到远程。
十四、pull 的原理是什么?
远程仓库比本地多了提交:
远程:
A ← B ← C ← D
本地:
A ← B ← C
执行:
git pull
大致相当于:
git fetch
git merge
即:
1. fetch:下载远程新提交
2. merge:合并到当前分支
所以:
git pull
并不只是"下载代码",而是:
下载远程提交,并尝试合并到当前本地分支。
十五、clone 的原理是什么?
执行:
git clone 仓库地址
Git 通常会:
1. 下载远程仓库的提交历史
2. 下载文件内容对象
3. 创建本地 .git 仓库
4. 创建远程名称 origin
5. 检出默认分支
6. 生成工作区文件
所以 clone 不只是下载当前项目文件。
它通常还下载了项目的版本历史。
这也是为什么克隆完成后可以执行:
git log
git branch
git reset
十六、reset、restore、revert 的区别
这三个命令经常混淆。
1. git restore
主要用于恢复文件。
例如放弃工作区修改:
git restore UserController.java
意思是:
不要当前工作区修改
恢复成暂存区或提交中的版本
2. git reset
主要用于移动分支指针、调整暂存区。
例如:
git reset --soft HEAD~1
含义:
撤销最近一次提交
但代码仍保留在暂存区
git reset --mixed HEAD~1
含义:
撤销提交
代码保留在工作区
暂存区清空
git reset --hard HEAD~1
含义:
提交撤销
暂存区撤销
工作区修改也丢弃
其中 --hard 风险最大。
3. git revert
不是删除旧提交,而是创建一个反向提交。
例如:
A ← B ← C
C 有问题,执行:
git revert C
得到:
A ← B ← C ← D
D 的内容用于抵消 C 的修改。
所以:
reset:改写历史、移动指针
revert:保留历史、增加反向提交
公共分支一般优先使用 revert。
十七、.git 目录是什么?
执行:
git init
项目中会出现:
.git/
这就是 Git 仓库的核心数据库。
它里面大致保存:
.git/
├── objects/ Git 对象
├── refs/ 分支和标签指针
├── HEAD 当前所在分支
├── index 暂存区
├── config 仓库配置
└── logs/ 操作记录
因此:
项目之所以是 Git 仓库,是因为存在
.git目录。
如果删除 .git:
代码文件还在
但所有提交历史、分支、标签、仓库配置都会消失
项目就会变成普通文件夹。
十八、Git 底层保存的四类对象
从底层理解,Git 主要有四类对象。
1. blob
保存文件内容。
例如:
public class User {}
Git 会根据内容生成哈希,并存储为 blob。
2. tree
保存目录结构。
例如:
src/
├── User.java
└── File.java
tree 中记录:
文件名 → blob
目录名 → tree
3. commit
保存一次提交。
它指向:
项目根 tree
父 commit
作者
时间
提交信息
4. tag
通常用于给某个提交起一个固定名称。
例如:
v1.0.0
v2.0.0
十九、为什么 Git 中经常看到一串哈希?
例如:
a31f9c8
完整提交 ID 可能是:
a31f9c87d0e9...
Git 会根据对象内容计算哈希值。
这个哈希可以理解为:
Git 对象的内容身份证。
内容改变,哈希通常也会改变。
因此:
文件内容 → blob 哈希
目录结构 → tree 哈希
提交信息 → commit 哈希
提交中还包含父提交哈希,所以历史之间相互关联。
二十、用你实际的项目串起来
假设你的 AIWear 项目最开始是:
提交 A:完成基础项目搭建
然后完成用户登录:
提交 B:完成 JWT 登录鉴权
完成图片上传:
提交 C:完成图片上传与 OSS 存储
完成图片检索:
提交 D:完成 CLIP 图片检索
Git 历史:
A ← B ← C ← D
↑
main
↑
HEAD
现在你想试验一个新功能,但怕改坏:
git switch -c feature-image-replace
得到:
A ← B ← C ← D
↑
main
↑
feature-image-replace
↑
HEAD
你在新分支提交两次:
A ← B ← C ← D
├── E ← F
│ ↑
│ feature-image-replace
↑
main
测试成功后合并:
git switch main
git merge feature-image-replace
这就是 Git 的典型工作方式:
稳定版本留在 main
新功能在独立分支开发
完成后再合并
二十一、最核心的命令与原理对应
| 命令 | 本质 |
|---|---|
git init |
创建 .git 本地仓库 |
git status |
比较工作区、暂存区和最新提交 |
git add |
将内容放入暂存区 |
git commit |
基于暂存区创建新快照 |
git log |
查看提交链 |
git branch |
创建或查看提交指针 |
git switch |
切换分支,并更新工作区 |
git merge |
合并两条提交历史 |
git clone |
复制远程仓库和历史到本地 |
git fetch |
下载远程新提交,不自动合并 |
git pull |
下载远程提交并合并 |
git push |
上传本地新增提交 |
git restore |
恢复文件内容 |
git reset |
移动分支指针或重置暂存区 |
git revert |
创建反向提交抵消旧提交 |
二十二、最后只记住这张图
git push
本地仓库 ─────────────────────→ 远程仓库 GitHub
↑ │
│ git commit │ git fetch / pull
│ ↓
暂存区
↑
│ git add
│
工作区
对应你的日常操作:
# 1. 修改代码
工作区变化
# 2. 选择要提交的内容
git add .
# 3. 生成本地版本
git commit -m "完成某功能"
# 4. 上传到 GitHub
git push
最准确的理解是:
git add是选择下一次快照内容,git commit是在本地生成快照,git push是把本地快照同步到远程。
而分支本质上只是指向某个提交的指针,Git 的整个历史就是由一个个 commit 连接起来的有向图。
后端程序员 Git 日常高频操作:按"目标"整理
日常开发中,不应该把 Git 命令孤立地记忆,而应该记成一套套工作流:
我要完成什么目标
→ 当前处于什么状态
→ 依次执行哪些命令
→ 每个命令改变了什么
先记住 Git 的几个核心区域:
工作区
│ git add
▼
暂存区
│ git commit
▼
本地仓库
│ git push
▼
远程仓库
一、第一次把远程项目下载到本地
目标
公司已经有一个后端项目,例如:
https://github.com/company/order-service.git
你要把它下载到本机,然后开始开发。
指令链
git clone https://github.com/company/order-service.git
cd order-service
git status
git branch -a
git remote -v
每一步的含义
1. 克隆远程仓库
git clone https://github.com/company/order-service.git
这条命令不只是下载文件,还会:
1. 下载项目当前文件
2. 下载 Git 提交历史
3. 创建本地 .git 仓库
4. 创建远程仓库别名 origin
5. 自动检出默认分支
克隆完成后,一般会得到:
order-service/
├── src/
├── pom.xml
├── README.md
└── .git/
2. 进入项目
cd order-service
Git 命令通常必须在 Git 仓库目录或其子目录内执行。
3. 查看当前状态
git status
可能输出:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
含义:
当前分支:main
本地 main 与远程 main 一致
工作区没有未提交修改
4. 查看所有分支
git branch -a
其中:
git branch
只看本地分支。
git branch -r
只看远程分支。
git branch -a
同时查看本地和远程分支。
5. 查看远程仓库地址
git remote -v
可能输出:
origin https://github.com/company/order-service.git (fetch)
origin https://github.com/company/order-service.git (push)
origin 是远程仓库的默认别名,不是固定关键字,只是约定俗成的名称。
二、每天开始开发前,同步远程最新代码
目标
上班后准备开发,但其他同事昨天可能已经向远程分支提交了新代码。
你需要先更新本地代码,避免基于旧版本继续开发。
推荐指令链
git switch main
git status
git pull --ff-only origin main
如果团队主分支叫 develop:
git switch develop
git status
git pull --ff-only origin develop
每一步的含义
1. 切换到主开发分支
git switch main
或者:
git switch develop
switch 专门用于切换分支,比旧命令更清晰:
git checkout main
旧命令仍然能用,但现在更推荐:
git switch main
2. 确认工作区是否干净
git status
如果存在未提交修改,直接切换或拉取代码可能产生冲突。
理想状态:
nothing to commit, working tree clean
3. 拉取远程最新提交
git pull --ff-only origin main
其中:
git pull
≈
git fetch + git merge
--ff-only 表示:
只允许快进更新,不允许 Git 自动产生额外的合并提交。
假设:
远程:A ← B ← C
本地:A ← B
拉取后:
A ← B ← C
↑
main
这叫 fast-forward,主分支历史比较干净。
如果本地和远程已经各自有新提交,--ff-only 会直接拒绝,而不是擅自合并。
三、从主分支创建一个功能分支
目标
你要开发一个新功能,例如:
新增订单退款接口
不应该直接在 main 或 develop 上乱改,而应该创建独立功能分支。
指令链
git switch develop
git pull --ff-only origin develop
git switch -c feature/refund-api
含义
1. 先进入基准分支
git switch develop
功能分支通常要从团队最新的开发分支创建。
2. 同步最新代码
git pull --ff-only origin develop
保证新分支基于最新版本创建。
3. 创建并切换新分支
git switch -c feature/refund-api
等价于:
git branch feature/refund-api
git switch feature/refund-api
-c 表示 create。
创建前:
A ← B ← C
↑
develop
创建后:
A ← B ← C
↑
develop
↑
feature/refund-api
↑
HEAD
分支本质上只是指向提交的指针,并不是复制一整份项目。
四、完成一次规范的代码提交
目标
你完成了退款接口相关开发,现在要提交代码。
推荐指令链
git status
git diff
git add src/main/java/com/example/order/controller/RefundController.java
git add src/main/java/com/example/order/service/RefundService.java
git diff --cached
git commit -m "feat(order): add refund API"
git status
第一步:查看修改状态
git status
它会告诉你:
哪些文件被修改
哪些文件是新增文件
哪些文件已进入暂存区
哪些文件尚未暂存
例如:
Changes not staged for commit:
modified: RefundService.java
Untracked files:
RefundController.java
常见状态
| 状态 | 含义 |
|---|---|
modified |
已被 Git 管理的文件发生修改 |
untracked |
新文件,Git 尚未跟踪 |
deleted |
文件被删除 |
staged |
已进入暂存区,准备提交 |
第二步:查看尚未暂存的修改
git diff
它比较的是:
工作区
vs
暂存区
也就是查看你改了什么,但还没有执行 git add 的内容。
如果只想查看某个文件:
git diff src/main/java/com/example/order/service/RefundService.java
第三步:把本次需要提交的文件加入暂存区
git add RefundController.java
git add RefundService.java
含义:
把这些文件当前版本放入暂存区,准备进入下一次提交。
不建议无脑执行:
git add .
因为它可能把这些内容一起提交:
调试日志
临时文件
IDE 配置
错误修改
敏感配置
不过在你确认整个工作区修改都属于同一个目标时,可以使用:
git add .
或者:
git add -A
区别通常不大,git add -A 会统一处理新增、修改和删除。
第四步:检查暂存区内容
git diff --cached
或者:
git diff --staged
两者等价。
它比较的是:
暂存区
vs
上一次提交
这条命令非常重要,它回答的是:
我下一次 commit 到底会提交什么?
第五步:生成提交
git commit -m "feat(order): add refund API"
提交后,Git 会基于暂存区创建一个新提交。
提交信息建议采用类似规范:
类型(模块): 简要描述
常见类型:
| 类型 | 含义 |
|---|---|
feat |
新功能 |
fix |
修复 Bug |
refactor |
重构,不改变外部功能 |
docs |
文档修改 |
test |
测试代码 |
chore |
构建、配置、依赖等杂项 |
perf |
性能优化 |
style |
格式调整,不涉及业务逻辑 |
示例:
git commit -m "feat(user): add SMS login"
git commit -m "fix(order): prevent duplicate payment"
git commit -m "refactor(file): extract OSS upload service"
git commit -m "test(refund): add refund service unit tests"
五、把本地功能分支推送到远程
目标
本地已经有:
feature/refund-api
现在要推送到远程,供其他人查看或创建 Pull Request。
第一次推送
git push -u origin feature/refund-api
其中:
origin
表示远程仓库。
feature/refund-api
表示要推送的本地分支。
-u
表示建立本地分支与远程分支的跟踪关系。
建立后,以后只需要:
git push
不必每次都写:
git push origin feature/refund-api
后续推送
git push
注意:
commit 只保存到本地
push 才会同步到远程
完整关系:
改代码
→ git add
→ git commit
→ git push
六、开发中途临时保存修改,但暂时不想提交
目标
你正在开发退款功能,代码还没写完。
突然需要:
切换到 main 修复线上紧急 Bug
当前代码又不适合提交。
这时使用 stash。
指令链
git status
git stash push -u -m "WIP: refund API"
git switch main
git pull --ff-only origin main
处理完其他任务后:
git switch feature/refund-api
git stash list
git stash pop
stash 的含义
git stash
相当于:
暂时把当前工作区修改收起来,让工作区恢复干净。
推荐写法
git stash push -u -m "WIP: refund API"
参数含义:
push:创建一个 stash
-u:连未跟踪的新文件一起保存
-m:添加说明
WIP:Work In Progress,表示尚未完成
如果不加 -u,新创建但未跟踪的文件可能不会进入 stash。
查看 stash
git stash list
输出类似:
stash@{0}: On feature/refund-api: WIP: refund API
stash@{1}: On feature/login: WIP: login validation
恢复并删除 stash
git stash pop
含义:
恢复最近一次 stash
并从 stash 列表中删除它
恢复但保留 stash
git stash apply stash@{0}
apply 只恢复,不删除。
删除某个 stash
git stash drop stash@{0}
删除所有 stash
git stash clear
这条命令风险较高,执行后通常不容易恢复。
七、只暂存一个文件中的部分修改
目标
你在同一个文件中同时做了两件事:
1. 修复空指针 Bug
2. 顺手重构了一段代码
你只想先提交 Bug 修复。
指令
git add -p RefundService.java
-p 表示 patch,Git 会把修改分块展示,并询问:
Stage this hunk [y,n,q,a,d,s,e,?]?
常见选项:
| 选项 | 含义 |
|---|---|
y |
暂存这一块 |
n |
不暂存这一块 |
q |
退出 |
a |
暂存当前文件后续所有修改块 |
d |
不暂存当前文件后续所有修改块 |
s |
把当前修改块进一步拆分 |
e |
手工编辑修改块 |
然后检查:
git diff --cached
再提交:
git commit -m "fix(refund): handle null refund record"
这是组织高质量提交非常实用的命令。
八、撤销工作区中尚未暂存的修改
目标
你改坏了一个文件,而且还没有执行 git add。
想恢复到修改之前。
撤销单个文件
git restore RefundService.java
含义:
用暂存区中的版本覆盖工作区中的这个文件。
如果暂存区没有特殊版本,通常就会恢复到最近一次提交状态。
撤销全部未暂存修改
git restore .
风险:
工作区未暂存的修改会被直接丢弃
执行前先检查:
git diff
九、撤销已经执行的 git add
目标
你误执行了:
git add .
一些文件不应该进入下一次提交。
撤销单个文件暂存
git restore --staged application.yml
含义:
文件仍然保留在工作区
只是从暂存区移除
不会删除你的代码修改。
撤销全部暂存
git restore --staged .
也可以使用旧写法:
git reset HEAD .
推荐优先使用语义更清晰的:
git restore --staged .
十、刚提交完,发现漏了一个文件
目标
你刚执行:
git commit -m "feat(order): add refund API"
但发现漏提交了:
RefundRequest.java
指令链
git add RefundRequest.java
git commit --amend --no-edit
含义:
把新增暂存内容补进最近一次提交
保留原提交信息不变
如果还想修改提交信息:
git commit --amend -m "feat(order): add refund application API"
重要风险
--amend 不是修改原提交,而是创建一个新的提交替换它。
因此提交哈希会改变。
安全场景
最近一次提交还没有 push
谨慎场景
最近一次提交已经 push,且其他人可能已经基于它开发
公共历史尽量不要随意 amend。
十一、撤销最近一次本地提交,但保留代码
目标
刚刚提交错了,但还没有推送。
你希望:
提交记录撤销
代码修改保留
方案一:保留在暂存区
git reset --soft HEAD~1
结果:
最近一次 commit 被撤销
代码仍然处于 staged 状态
适合:
提交信息写错
需要重新组织提交
漏了一些文件
方案二:保留在工作区,取消暂存
git reset HEAD~1
默认是 --mixed:
git reset --mixed HEAD~1
结果:
提交撤销
暂存区清空
代码修改仍保留在工作区
方案三:提交和代码一起丢弃
git reset --hard HEAD~1
结果:
最近提交被撤销
暂存区内容丢弃
工作区修改也丢弃
这是危险命令。
执行前必须确认:
git log --oneline -5
git status
十二、已经推送到远程后,安全撤销某次提交
目标
某个 Bug 提交已经推送到团队公共分支。
不能随意改写历史,需要生成一条"反向提交"。
查看提交历史
git log --oneline
例如:
9f21a6d fix(order): incorrect refund amount
73c0c3a feat(order): add refund API
撤销指定提交
git revert 9f21a6d
Git 会创建一个新提交,抵消 9f21a6d 的修改。
历史由:
A ← B ← C
变为:
A ← B ← C ← D
其中 D 是对 C 的反向修改。
然后推送:
git push
reset 和 revert 的区别
| 命令 | 原理 | 适用场景 |
|---|---|---|
reset |
移动分支指针,可能改写历史 | 尚未共享的本地提交 |
revert |
创建新提交抵消旧提交 | 已推送的公共提交 |
后端团队开发中:
已推送到公共分支的错误提交,优先使用
git revert。
十三、把远程最新代码同步到功能分支
目标
你开发了几天,期间 develop 分支已经被其他人更新。
现在想把最新 develop 合入自己的功能分支。
有两种常见方案。
方案一:merge,安全直观
git switch develop
git pull --ff-only origin develop
git switch feature/refund-api
git merge develop
如果没有冲突,可能生成合并提交:
Merge branch 'develop' into feature/refund-api
优点:
不改写现有历史
安全
容易理解
适合已推送、多人共同开发的分支
缺点:
可能产生较多 merge commit
提交图相对复杂
方案二:rebase,历史更线性
git switch develop
git pull --ff-only origin develop
git switch feature/refund-api
git rebase develop
假设原来:
A ← B ← C ← D develop
\
E ← F feature
rebase 后:
A ← B ← C ← D ← E' ← F'
Git 会把功能分支提交重新放到最新 develop 后面。
优点:
提交历史线性、整洁
缺点:
会重写 E、F,生成新的 E'、F'
提交哈希发生变化
rebase 的安全原则
适合:
自己的私人功能分支
尚未被其他人基于其开发
谨慎:
多人共享分支
公共主分支
已经被大量同事拉取的提交
十四、解决 merge 或 rebase 冲突
目标
同步代码时出现冲突。
例如:
git merge develop
输出:
CONFLICT (content): Merge conflict in RefundService.java
第一步:查看冲突文件
git status
可能显示:
both modified: RefundService.java
第二步:打开文件解决冲突
冲突标记类似:
<<<<<<< HEAD
return refundRepository.findById(id);
=======
return refundRepository.findByOrderId(orderId);
>>>>>>> develop
含义:
<<<<<<< HEAD
当前分支内容
=======
分隔线
>>>>>>> develop
被合并分支内容
你必须根据业务逻辑修改为最终结果,例如:
return refundRepository.findByOrderId(orderId);
并删除所有冲突标记。
第三步:标记冲突已经解决
git add RefundService.java
注意:这里的 git add 不只是"暂存",还表示:
这个冲突文件我已经人工处理完毕。
merge 冲突处理完成
git commit
或者某些情况下:
git merge --continue
如果不想继续合并:
git merge --abort
它会尝试恢复到 merge 前的状态。
rebase 冲突处理完成
git add RefundService.java
git rebase --continue
如果后续还有冲突,重复:
修改文件
→ git add
→ git rebase --continue
放弃本次 rebase:
git rebase --abort
跳过当前提交:
git rebase --skip
--skip 可能丢掉当前提交的修改,不能随便使用。
十五、rebase 后如何推送远程分支
目标
你的功能分支已经推送过远程,后来又执行了:
git rebase develop
因为提交哈希已经改变,普通 push 会被拒绝。
推荐命令
git push --force-with-lease
不推荐:
git push --force
区别:
--force
无条件强制覆盖远程分支。
可能把别人刚推送的提交覆盖掉。
--force-with-lease
只有远程分支仍然是你预期的状态时,才允许强制推送。
如果其他人已推送新提交,它会拒绝覆盖。
因此:
确实需要强推时,优先使用
--force-with-lease。
仍然只适用于自己的功能分支,不要随便对公共分支使用。
十六、只把另一个分支的某个提交拿过来
目标
另一个分支修复了一个公共 Bug,但你不想合并整个分支,只想拿其中一个提交。
使用 cherry-pick。
找到目标提交
git log --oneline feature/payment-fix
假设目标提交是:
a82c13f fix(payment): prevent duplicate callback
切换到目标分支
git switch feature/refund-api
拿取指定提交
git cherry-pick a82c13f
Git 会把该提交所包含的修改应用到当前分支,并生成一个新提交。
原提交和新提交内容相似,但哈希通常不同。
多个连续提交
git cherry-pick a82c13f b93d128
或者一个区间:
git cherry-pick a82c13f^..b93d128
冲突处理
git status
解决冲突后:
git add .
git cherry-pick --continue
放弃:
git cherry-pick --abort
十七、查看提交历史和某次代码变更
目标
排查:
这个接口什么时候改的?
谁改的?
哪次提交引入了 Bug?
简洁查看提交历史
git log --oneline
图形化查看分支历史
git log --oneline --graph --decorate --all
示例:
* a93f2bd (HEAD -> feature/refund-api) feat: add refund API
| * 61f8a03 (develop) fix: update payment callback
|/
* 2ca31de init project
参数含义:
| 参数 | 含义 |
|---|---|
--oneline |
每个提交显示一行 |
--graph |
显示分支图 |
--decorate |
显示分支、标签、HEAD |
--all |
查看所有分支 |
查看某次提交详情
git show a93f2bd
它会展示:
提交作者
提交时间
提交说明
具体代码差异
只看统计:
git show --stat a93f2bd
查看某个文件的提交历史
git log --oneline -- RefundService.java
查看文件每次具体变化:
git log -p -- RefundService.java
查看某一行是谁写的
git blame RefundService.java
输出会标记:
每一行对应的提交
作者
时间
git blame 主要用于追踪代码来源,不应该被理解成"追责工具"。
十八、比较两个分支之间的差异
目标
你想知道:
功能分支比 develop 多改了什么?
查看代码差异
git diff develop...feature/refund-api
三个点 ... 通常表示:
从两个分支共同祖先开始,查看功能分支新增的变化。
如果当前就在功能分支:
git diff develop...HEAD
查看提交差异
git log --oneline develop..feature/refund-api
含义:
查看 feature/refund-api 中有
但 develop 中没有的提交
只看修改文件统计
git diff --stat develop...HEAD
输出类似:
RefundController.java | 42 +++++++++
RefundService.java | 81 +++++++++++++
2 files changed, 123 insertions(+)
十九、删除已经合并完成的功能分支
目标
Pull Request 已经合并,功能分支不再需要。
删除本地分支
先切换到其他分支:
git switch develop
然后:
git branch -d feature/refund-api
-d 是安全删除。
如果分支尚未合并,Git 会拒绝删除。
强制删除:
git branch -D feature/refund-api
-D 会无视是否合并,风险更高。
删除远程分支
git push origin --delete feature/refund-api
清理本地失效的远程分支引用
远程分支已经删除,但本地可能仍能看到:
origin/feature/refund-api
执行:
git fetch --prune
或者:
git remote prune origin
二十、新建本地仓库并上传到 GitHub
目标
你本地已有一个 Spring Boot 项目,现在要纳入 Git 管理并推送远程。
指令链
cd order-service
git init
git status
git add .gitignore
git add pom.xml
git add src/
git diff --cached
git commit -m "chore: initialize project"
git branch -M main
git remote add origin https://github.com/username/order-service.git
git push -u origin main
关键步骤解释
创建本地仓库
git init
生成:
.git/
从此该目录成为 Git 仓库。
重命名当前分支
git branch -M main
-M 表示强制重命名当前分支为 main。
关联远程仓库
git remote add origin 远程地址
含义:
给远程地址起别名 origin
查看:
git remote -v
首次推送
git push -u origin main
推送并建立跟踪关系。
二十一、修改远程仓库地址
目标
远程地址发生变化,例如:
HTTPS 改 SSH
仓库迁移
GitHub 改 GitLab
查看当前地址
git remote -v
修改地址
git remote set-url origin git@github.com:username/order-service.git
再次确认
git remote -v
删除远程关联
git remote remove origin
注意:
只删除本地对远程仓库的引用
不会删除远程仓库
也不会删除本地代码
二十二、忽略配置文件和敏感信息
目标
后端项目中这些内容不应该提交:
数据库密码
Redis 密码
OSS Secret
JWT 密钥
IDE 配置
target 目录
日志文件
环境变量文件
.gitignore 示例
# IDEA
.idea/
*.iml
# Java
target/
*.class
*.jar
*.log
# Maven
.mvn/timing.properties
# Sensitive configuration
application-local.yml
application-dev.yml
application-prod.yml
.env
# Runtime files
logs/
tmp/
# OS
.DS_Store
Thumbs.db
已经被 Git 跟踪的文件,新增 .gitignore 不生效
例如 application-dev.yml 已经提交过。
仅写:
application-dev.yml
是不够的。
还需要:
git rm --cached src/main/resources/application-dev.yml
含义:
从 Git 跟踪中移除
但保留本地文件
然后提交:
git add .gitignore
git commit -m "chore(config): stop tracking local configuration"
如果是整个目录:
git rm -r --cached logs/
重要提醒
如果密码已经推送到远程,仅删除文件并不能真正消除泄漏风险,因为密码仍在 Git 历史中。
应立即:
1. 作废旧密钥或修改密码
2. 更新配置
3. 必要时清理 Git 历史
首先应该轮换密钥,而不是只想着删除提交。
二十三、查看某个文件为什么没有被 Git 跟踪
目标
你执行:
git add application-local.yml
但文件没有进入暂存区。
可能是被 .gitignore 忽略了。
检查命令
git check-ignore -v src/main/resources/application-local.yml
它会告诉你:
是哪一个 .gitignore
哪一条规则
忽略了该文件
例如:
.gitignore:12:application-local.yml src/main/resources/application-local.yml
二十四、误删分支或误 reset 后恢复代码
目标
你误执行了:
git reset --hard HEAD~2
或者误删了分支。
Git 中很多操作仍可通过 reflog 恢复。
查看 HEAD 移动记录
git reflog
可能看到:
c81d2a1 HEAD@{0}: reset: moving to HEAD~2
af92b31 HEAD@{1}: commit: feat: add refund API
7283ca0 HEAD@{2}: commit: feat: add refund validation
说明 reset 前的提交是:
af92b31
创建恢复分支
git switch -c recovery/refund af92b31
这是相对安全的做法,不直接覆盖当前分支。
确认代码正确后,再决定如何合并。
也可以直接恢复当前分支:
git reset --hard af92b31
但应先确认没有其他需要保留的修改。
reflog 是后端开发中非常重要的"后悔药"。
二十五、清理未跟踪的临时文件
目标
编译、测试或脚本运行后产生了很多未跟踪文件。
先预览:
git clean -n
或者:
git clean --dry-run
只显示将被删除的文件,不真正删除。
删除未跟踪文件
git clean -f
同时删除未跟踪目录
git clean -fd
连 .gitignore 忽略的文件也删除
git clean -fdx
这条命令极其危险,可能删除:
本地 application-local.yml
.env
IDE 文件
构建产物
本地测试数据
因此日常开发一般不要随便使用:
git clean -fdx
必须先执行:
git clean -ndx
进行预览。
二十六、给正式版本打标签
目标
后端服务正式发布,例如:
v1.2.0
需要给某个提交打标签,方便部署和回滚。
推荐使用附注标签
git tag -a v1.2.0 -m "release v1.2.0"
查看标签:
git tag
查看标签详情:
git show v1.2.0
推送一个标签:
git push origin v1.2.0
推送所有标签:
git push origin --tags
删除本地标签:
git tag -d v1.2.0
删除远程标签:
git push origin --delete v1.2.0
标签和分支的区别:
分支:通常会随着提交继续移动
标签:通常固定指向某个版本提交
二十七、临时切换到历史提交查看旧代码
目标
你想查看旧版本的实现,但不打算立即修改。
查看历史
git log --oneline
临时切换到指定提交
git switch --detach a82c13f
此时进入 detached HEAD 状态:
HEAD 直接指向某个提交
而不是指向某个分支
适合:
查看旧代码
运行旧版本
排查历史问题
查看完成后返回:
git switch develop
如果在 detached HEAD 状态下产生了新提交,应该及时创建分支保存:
git switch -c fix/from-old-version
否则这些提交未来可能难以找到。
二十八、二分查找定位引入 Bug 的提交
目标
你知道:
当前版本有 Bug
某个旧版本没有 Bug
但中间有大量提交,不知道是哪一次引入的。
使用:
git bisect
操作流程
开始:
git bisect start
标记当前版本是坏的:
git bisect bad
标记某个旧提交是好的:
git bisect good a82c13f
Git 会自动切换到中间提交。
你运行测试后,如果该提交正常:
git bisect good
如果存在 Bug:
git bisect bad
反复几次,Git 会定位第一个坏提交。
结束:
git bisect reset
这相当于对提交历史做二分查找。
适合排查:
接口何时开始报错
性能回退从哪次提交开始
某个单元测试从哪次开始失败
配置兼容性问题由哪个提交引入
二十九、提交前临时检查是否误提交敏感内容
目标
提交 Spring Boot 项目前,检查是否包含密码、密钥和环境配置。
查看暂存文件列表
git diff --cached --name-only
查看暂存内容
git diff --cached
搜索敏感关键词
在 Git Bash 中可以使用:
git diff --cached | grep -Ei "password|secret|token|api[_-]?key|access[_-]?key"
也可以查看当前已跟踪文件:
git ls-files
重点检查:
application.yml
application-dev.yml
.env
docker-compose.yml
数据库初始化脚本
私钥文件
云服务配置
三十、后端程序员推荐的完整日常工作流
假设团队使用:
主开发分支:develop
功能分支:feature/xxx
1. 开始新功能
git switch develop
git pull --ff-only origin develop
git switch -c feature/refund-api
2. 开发过程中检查修改
git status
git diff
3. 选择本次提交内容
git add src/main/java/com/example/order/controller/RefundController.java
git add src/main/java/com/example/order/service/RefundService.java
git add src/test/java/com/example/order/RefundServiceTest.java
4. 检查待提交内容
git diff --cached
5. 本地运行测试
Maven 项目:
mvn test
或者 Maven Wrapper:
./mvnw test
Windows:
mvnw.cmd test
6. 创建提交
git commit -m "feat(order): add refund application API"
7. 推送远程
第一次:
git push -u origin feature/refund-api
后续:
git push
8. 开发期间同步 develop
git fetch origin
git rebase origin/develop
或者使用 merge:
git merge origin/develop
9. 解决冲突后继续
rebase:
git add .
git rebase --continue
merge:
git add .
git commit
10. rebase 后更新远程分支
git push --force-with-lease
11. PR 合并后清理
git switch develop
git pull --ff-only origin develop
git branch -d feature/refund-api
git fetch --prune
如果远程没有自动删除分支:
git push origin --delete feature/refund-api
三十一、紧急线上 Bug 修复工作流
目标
线上出现支付回调重复处理问题,需要从正式分支快速修复。
假设正式分支是 main。
指令链
git switch main
git pull --ff-only origin main
git switch -c hotfix/payment-idempotency
修改代码后:
git status
git diff
git add src/main/java/com/example/payment/PaymentCallbackService.java
git diff --cached
mvn test
git commit -m "fix(payment): make callback processing idempotent"
git push -u origin hotfix/payment-idempotency
合并上线后,需要把修复同步回开发分支:
git switch develop
git pull --ff-only origin develop
git merge main
git push origin develop
或者把修复提交 cherry-pick 到 develop:
git switch develop
git pull --ff-only origin develop
git cherry-pick <hotfix提交哈希>
git push
具体使用 merge 还是 cherry-pick,应遵循团队分支策略。
三十二、常见错误场景速查
1. 提交信息写错了,尚未 push
git commit --amend -m "正确的提交信息"
2. 漏提交一个文件,尚未 push
git add 漏掉的文件
git commit --amend --no-edit
3. git add 加多了
git restore --staged 不该提交的文件
4. 文件改坏了,还没有 add
git restore 文件名
5. 本地最后一次提交不想要了,但保留代码
git reset HEAD~1
6. 已推送的提交需要撤销
git revert 提交哈希
git push
7. 需要临时切换分支,代码未完成
git stash push -u -m "WIP"
git switch 其他分支
回来后:
git switch 原分支
git stash pop
8. 拉取代码发生冲突
git status
人工处理后:
git add 冲突文件
git commit
或者 rebase 情况:
git add 冲突文件
git rebase --continue
9. 误 reset 或误删分支
git reflog
git switch -c recovery 分支丢失前的提交哈希
10. rebase 后 push 被拒绝
git push --force-with-lease
仅限明确知道自己正在重写个人功能分支历史时使用。
三十三、建议配置的 Git 别名
高频命令可以配置别名:
git config --global alias.st status
git config --global alias.sw switch
git config --global alias.br branch
git config --global alias.co checkout
git config --global alias.cm commit
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "restore --staged"
配置后:
git st
git sw develop
git lg
git unstage application.yml
查看全局配置:
git config --global --list
设置用户名和邮箱:
git config --global user.name "Your Name"
git config --global user.email "your@email.com"
三十四、后端开发中最需要掌握的命令优先级
第一层:必须熟练
git status
git diff
git add
git commit
git push
git pull
git switch
git branch
git log
这是正常开发的基础闭环。
第二层:必须会处理问题
git restore
git reset
git revert
git stash
git merge
git rebase
git reflog
这是处理误操作、冲突和分支同步的核心。
第三层:提高开发效率
git add -p
git cherry-pick
git blame
git bisect
git clean
git tag
这是代码审查、问题定位和发布管理能力。
三十五、最终记住这几套最常用目标
新建功能分支
git switch develop
git pull --ff-only origin develop
git switch -c feature/xxx
正常提交并推送
git status
git diff
git add 指定文件
git diff --cached
git commit -m "feat(module): description"
git push
临时保存未完成代码
git stash push -u -m "WIP: xxx"
git switch 其他分支
恢复:
git switch 原分支
git stash pop
同步最新开发分支
git fetch origin
git rebase origin/develop
或者:
git merge origin/develop
撤销未提交修改
git restore 文件名
取消暂存
git restore --staged 文件名
撤销本地提交但保留代码
git reset HEAD~1
撤销已经推送的提交
git revert 提交哈希
git push
恢复误删提交
git reflog
git switch -c recovery 提交哈希
Git 日常操作的核心不是背几十条命令,而是始终先判断这三个问题:
1. 我的修改现在位于工作区、暂存区,还是已经 commit?
2. 这个提交是否已经 push,被其他人共享?
3. 我是想保留代码、丢弃代码,还是仅修改提交历史?
只要这三个问题判断清楚,绝大多数 Git 命令就不会用错。