Git 命令并不难,真正容易混乱的是:一条命令究竟改变了工作区、暂存区、提交历史,还是远程仓库?如果没有这层判断,restore、reset、revert 看起来都像“撤销”,fetch、pull 也像是同一种“拉代码”。
本文先建立四个区域的心智模型,再把常用命令放进完整开发流程。示例使用 main 作为主分支、origin 作为远程名称;实际项目应以仓库约定为准。
先理解 Git 的四个区域
Git 保存的是一系列快照。日常操作主要发生在以下四个位置:
| 区域 | 保存什么 | 常见相关命令 |
|---|---|---|
| 工作区(working tree) | 当前磁盘上实际可编辑的文件 | status、diff、restore |
| 暂存区(index) | 下一次提交准备包含的快照 | add、diff --staged、restore --staged |
| 本地仓库 | 提交对象、分支和 HEAD | commit、log、branch、merge、rebase、reset |
| 远程仓库 | 团队共享的提交和分支引用 | fetch、pull、push |
flowchart LR W[工作区] -->|git add| I[暂存区] I -->|git commit| L[本地分支] L -->|git push| R[远程仓库] R -->|git fetch| RT[本地的远程跟踪分支] RT -->|merge 或 rebase| L
HEAD 通常指向当前分支,分支再指向该分支最新的提交。origin/main 是本地保存的远程跟踪引用,它记录上次 fetch 后你所知道的远程 main 状态,并不是实时读取远程服务器。
理解这四个区域后,每次操作前只需先问:问题发生在哪一层,我希望保留哪些内容?
修改前后都先检查
git status:当前状态的入口
git status 展示当前分支、工作区修改、暂存内容和未跟踪文件。--short 使用紧凑格式,--branch 增加分支及上下游信息。
git statusgit status --short --branch短格式左列表示暂存区状态,右列表示工作区状态。例如 M app.ts 表示修改已暂存, M app.ts 表示修改尚未暂存,MM app.ts 表示暂存后又继续修改。
注意:status 不显示完整差异。提交、切换分支、合并、rebase 或撤销前,都应继续使用 diff 检查内容。
git diff:比较不同区域
git diff 默认比较工作区与暂存区;--staged(也叫 --cached)比较暂存区与 HEAD;指定两个引用可以比较提交。
git diffgit diff --stagedgit diff main...feature/logingit diff -- src/auth.ts三个点 main...feature/login 常用于查看从共同祖先开始,功能分支引入了哪些变化。比较语义会随参数形式变化,不确定时先阅读输出和 git diff --help,不要只看统计数字。
git log:理解提交历史
git log 查看提交记录。--oneline 压缩为单行,--graph 绘制分支图,--decorate 显示引用,--all 包含所有本地引用。
git log --oneline --decorate -10git log --oneline --graph --decorate --allgit log -- src/auth.tslog 展示的是当前本地已有的信息。想判断远程是否有新提交,应先 git fetch,再查看 origin/main。
从修改到一次清晰提交
git add:把选定内容放入暂存区
git add <路径> 把指定文件当前内容写入暂存区;-p 可以逐块选择,-u 只暂存已跟踪文件的修改与删除。
git add src/auth.ts tests/auth.test.tsgit add -p src/auth.tsgit diff --stagedgit add 不是把文件永久交给 Git,也不是上传远程,而是在准备“下一次提交的快照”。暂存后继续编辑文件,会同时存在已暂存和未暂存两部分。
git commit:创建本地提交
git commit 根据暂存区创建提交。-m 提供提交信息,--amend 替换当前分支最近一次提交。
git commit -m "feat: add login validation"
# 仅修正尚未共享的最近一次提交git add src/auth.tsgit commit --amend提交只发生在本地仓库,不等于已经推送。提交信息应描述修改目的,而不是只写“update”或文件名。--amend 会创建新的提交并移动分支引用,因此已经推送或被他人基于其开发的提交不宜随意 amend。
使用分支完成一项开发任务
git branch:查看和管理分支引用
不带参数的 git branch 列出本地分支;-vv 同时显示提交和上游;-d 仅删除已合并分支,-D 强制删除。
git branchgit branch -vvgit branch -d feature/login删除分支只是删除引用,不会自动删除工作区文件,也不会删除远程同名分支。优先使用 -d;-D 可能让尚未合并的提交失去直接引用,使用前先确认提交仍被保留在其他分支或确实不再需要。
git switch:切换或创建分支
git switch <分支> 切换已有分支,-c 创建并切换,-C 会强制重置或创建目标分支,风险更高。
git switch maingit switch -c feature/logingit switch -旧教程经常用 git checkout 同时负责切换分支和恢复文件。现代 Git 可以用职责更单一的 switch 处理分支、用 restore 处理文件。切换前先查看 status;如果本地修改会被覆盖,Git 通常会拒绝,但不要把这一保护当作备份。
git merge:合并两条历史
在接收变更的分支上运行 git merge <来源分支>。--ff-only 只允许快进,--no-ff 在可快进时仍创建合并提交,具体策略应遵循团队约定。
git switch maingit merge --ff-only feature/login发生冲突时,文件中可能出现 <<<<<<<、=======、>>>>>>> 标记。应理解双方意图后编辑为最终内容,再暂存并完成合并:
git statusgit add src/auth.tsgit commit如果还没有完成合并并决定放弃,可以使用:
git merge --abort不要机械选择“ours”或“theirs”;语法上无冲突也不代表业务逻辑正确,合并后仍要运行测试。
获取和理解远程更新
git fetch:只更新远程信息
git fetch 下载远程对象并更新远程跟踪引用,通常不改当前工作区。可以指定远程和分支,--prune 清理远程已经删除的跟踪引用。
git fetch origingit fetch --prune origingit log --oneline HEAD..origin/maingit diff HEAD...origin/mainfetch 后,你可以先检查再决定 merge 或 rebase。这种“先获取、后集成”的分步方式,比不了解差异就直接修改当前分支更容易控制。
git pull:获取后立即集成
git pull 先执行 fetch,再按配置或参数把远程分支集成到当前分支。为了让行为明确,可以显式选择策略:
git pull --ff-onlygit pull --rebase--ff-only只接受不需要额外合并提交的快进更新;出现分叉就停止。--rebase把本地提交重放到更新后的上游之上。
不同仓库或 Git 配置可能采用 merge、rebase 或只允许快进。不要把 pull 简单理解为“下载文件”,它可能改变当前分支历史并产生冲突。
git push:发布本地引用
git push 把本地对象和分支引用更新到远程。-u/--set-upstream 在首次推送时建立上游关系,之后可以简化命令。
git push -u origin feature/logingit push推送被拒绝通常说明远程包含本地未知的历史。先 fetch 并理解差异,不要把强制推送当作普通解决方法。删除远程分支使用 git push origin --delete <分支>,执行前要确认没有协作者仍依赖它。
临时保存尚未完成的工作
git stash:把工作区暂时收起
git stash push 保存已跟踪文件的工作区和暂存区变化;-m 添加说明,-u 同时包含未跟踪文件。list 查看条目,show -p 查看差异,apply 保留 stash,pop 成功应用后删除条目。
git stash push -m "wip: login validation"git stash listgit stash show -p stash@{0}git stash apply stash@{0}git stash pop stash@{0}需要连未跟踪文件一起保存时:
git stash push -u -m "wip: include new files"stash 可能在应用时产生冲突,也可能长期被遗忘;它不是远程备份,更不适合代替有名称、可协作的分支。执行 pop 前先保持工作区清晰,并记住它只有在成功应用后才移除对应条目。
使用 rebase 更新本地功能分支
git rebase <上游> 会找到当前分支相对上游独有的提交,再把它们重放到新的基点上。常见用途是让尚未共享的功能分支基于最新主分支:
git switch feature/logingit fetch origingit rebase origin/main如果发生冲突:
git status# 编辑并解决冲突git add src/auth.tsgit rebase --continue决定放弃整个过程时:
git rebase --abortmerge 保留分支汇合关系,rebase 让历史更线性;两者都能集成变更,没有对所有团队都正确的唯一选择。应按协作范围、审计要求和仓库约定决定。
根据错误所在区域选择撤销方式
“撤销”不是一条命令,而是一棵决策树:内容是否暂存?是否已经提交?提交是否已经共享?
git restore:恢复工作区或取消暂存
默认情况下,git restore <路径> 用暂存区内容覆盖工作区文件。--staged 把暂存区恢复为 HEAD 中的内容,但保留工作区修改;--source 可以指定其他提交。
# 丢弃尚未暂存的修改git restore -- src/auth.ts
# 取消暂存,但保留工作区修改git restore --staged -- src/auth.ts
# 交互式选择要恢复的修改块git restore -p -- src/auth.tsgit reset:移动当前分支并重设状态
git reset <提交> 会移动当前分支指向的位置,不同模式决定暂存区和工作区如何处理:
| 模式 | 移动 HEAD | 重设暂存区 | 覆盖工作区 |
|---|---|---|---|
--soft | 是 | 否 | 否 |
--mixed(默认) | 是 | 是 | 否 |
--hard | 是 | 是 | 是 |
# 撤回最近一次本地提交,内容仍保持暂存git reset --soft HEAD~1
# 撤回最近一次本地提交,内容留在工作区但取消暂存git reset --mixed HEAD~1对于已经推送并被他人使用的提交,reset 后再强推会改写共享历史,通常不应这样处理。
git revert:用新提交抵消旧提交
git revert <提交> 计算指定提交的反向修改,并创建一个新提交。历史保持向前发展,因此更适合撤销已经共享的变更。
git revert a1b2c3d如果后续代码已经依赖被撤销提交,revert 也可能发生冲突或产生业务问题。它撤销的是补丁效果,不会让后来的所有代码自动回到当时状态。
git reflog:寻找本地引用移动记录
git reflog 记录本地 HEAD 和引用近期如何移动,常用于找到误 reset 或误删分支前的提交。
git refloggit show HEAD@{2}确认目标提交后,应先创建保护分支:
git branch recovery/login <commit-id>reflog 主要是本地、会过期的恢复线索,不是备份机制。凭据一旦提交,即使随后删除提交,也应立即吊销或轮换,而不是只依赖历史重写。
选择性应用一个提交
git cherry-pick:把指定提交应用到当前分支
git cherry-pick <提交> 读取指定提交引入的变化,在当前分支创建一个新的对应提交。
git switch releasegit cherry-pick a1b2c3d冲突处理与 rebase 类似:解决文件、git add,再执行 git cherry-pick --continue;放弃使用 git cherry-pick --abort。
它适合把明确的修复移植到另一个维护分支,但不应替代正常的分支合并。重复 cherry-pick 同一逻辑可能产生重复修改或让历史关系更难理解。
强制推送为什么危险
历史经 amend、rebase 或 reset 改写后,普通 push 可能因非快进而被拒绝。--force 会尝试直接更新远程引用,可能覆盖别人刚推送的提交。
如果团队流程允许改写自己的功能分支,应在 fetch 和检查后使用保护更强的方式:
git fetch origingit log --oneline --graph --decorate HEAD origin/feature/logingit push --force-with-lease origin feature/login--force-with-lease 会在远程引用不符合本地预期时拒绝更新,但它不是绝对保险:错误的目标分支、陈旧认知或特殊参数仍可能造成问题。
四个完整开发场景
场景一:完成一个小功能并推送
git switch maingit pull --ff-onlygit switch -c feature/login-validation
# 编辑和测试git status --shortgit diffgit add src/auth.ts tests/auth.test.tsgit diff --stagedgit commit -m "feat: validate login input"git push -u origin feature/login-validation这条流程把同步、开发、审核、提交和发布分开,每一步都能检查当前状态。
场景二:提交 PR 前同步主分支
git switch feature/login-validationgit fetch origingit log --oneline HEAD..origin/maingit rebase origin/main
# 运行测试后,若该功能分支此前已经推送且允许改写:git push --force-with-lease如果团队要求保留合并历史,也可以使用 git merge origin/main,然后普通 push。选择哪种方式由团队约定决定。
场景三:修正尚未推送的错误提交
如果只是漏了一个文件:
git add missing-file.tsgit commit --amend如果要重新组织最近一次提交:
git reset --soft HEAD~1git restore --staged .git add src/auth.tsgit commit -m "feat: validate login input"git add tests/auth.test.tsgit commit -m "test: cover login validation"这些操作改写历史,因此前提是提交尚未共享。
场景四:安全撤销已经发布的提交
git switch maingit pull --ff-onlygit log --oneline -10git revert a1b2c3d
# 检查、测试后git push这样会保留原提交和撤销提交,协作者不需要重写自己的历史。
日常检查清单
- 开始工作前使用
git status,同步远程前先确认当前分支。 - 用
git diff审查工作区,用git diff --staged审查下一次提交。 git add暂存明确路径,让一次提交只表达一个目的。fetch只更新远程信息;pull还会立即集成,使用时明确策略。restore主要处理文件内容,reset移动本地历史,revert用新提交撤销共享历史。- 只整理尚未共享的历史;rebase、amend 和 reset 后先判断是否需要协调。
- 把
--force-with-lease当作受限工具,而不是普通 push 的替代品。 - Git 不是未提交文件的备份。重要工作应及时形成提交并推送到受控远程。
当你能准确回答“这条命令将改变哪一层”,Git 就不再是一组需要死记的咒语,而是一套可预测、可检查的状态转换。
延伸阅读
分享文章
生成精美分享图或复制链接,与更多人分享本文。
继续阅读
换条路线
从其他文章中稳定抽取
最后更新于 ,距今已过 11 天
部分内容可能已过时